/*

    Licensed under the Apache License, Version 2.0 (the "License"); 
    you may not use this file except in compliance with the License. 
    You may obtain a copy of the License at 
    http://www.apache.org/licenses/LICENSE-2.0 
    Unless required by applicable law or agreed to in writing, software 
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    See the License for the specific language governing permissions and 
    limitations under the License.
      
 */
package jforex.mql;

import java.awt.Color;
import java.io.File;
import java.io.RandomAccessFile;
import java.lang.reflect.Array;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.TimeZone;

import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileSystemView;

import jforex.advanced.StateMachineStrategy;
import jforex.api.IBox;
import jforex.api.ISettings;
import jforex.impl.JFToolBox;

import com.dukascopy.api.Configurable;
import com.dukascopy.api.IAccount;
import com.dukascopy.api.IBar;
import com.dukascopy.api.IChart;
import com.dukascopy.api.IChartObject;
import com.dukascopy.api.IConsole;
import com.dukascopy.api.IContext;
import com.dukascopy.api.IEngine;
import com.dukascopy.api.IHistory;
import com.dukascopy.api.IIndicators;
import com.dukascopy.api.IMessage;
import com.dukascopy.api.IOrder;
import com.dukascopy.api.IStrategy;
import com.dukascopy.api.ITick;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.JFException;
import com.dukascopy.api.OfferSide;
import com.dukascopy.api.Period;
import com.dukascopy.api.IChartObject.ATTR_BOOLEAN;
import com.dukascopy.api.IChartObject.ATTR_COLOR;
import com.dukascopy.api.IChartObject.ATTR_DOUBLE;
import com.dukascopy.api.IChartObject.ATTR_INT;
import com.dukascopy.api.IChartObject.ATTR_LONG;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.IIndicators.MaType;
import com.dukascopy.api.indicators.IIndicator;

/**
 * 
 * This class is main class in converting biz. It's parent for all converted
 * strategies and contains JForex implementation MQL functions Not all for a
 * while but I hope will be so with community help. Please report about all
 * required functions which is not implemented yet)
 * 
 * 
 */
public abstract class MQLBridge implements IStrategy {

    @Configurable("Instrument")
    public Instrument currentInstrument = Instrument.EURUSD;

    @Configurable("Period")
    public Period currentPeriod = Period.ONE_MIN;

    // @Configurable("Debug log")
    public boolean mqldbg = false;

    //
    // private String currentInstrumentMQL = null;

    private int currentPeriodMQL = 0;

    protected IContext context = null;

    protected IConsole console = null;

    protected IEngine engine = null;

    protected IHistory history = null;

    protected IChart chart = null;

    protected IIndicators indicators = null;

    protected IBox box = null;

    private MQLAlertDialog alertDialog = null;

    // file handlers
    private Map<Integer, Character> filesDelimiters = new HashMap<Integer, Character>();

    private Map<Integer, RandomAccessFile> filesMap = new HashMap<Integer, RandomAccessFile>();

    public void onStart(IContext context) throws JFException {
        this.console = context.getConsole();
        this.context = context;
        this.engine = context.getEngine();
        this.history = context.getHistory();
        this.indicators = context.getIndicators();

        if (currentInstrument == null) {
            console.getErr().print("instrument not defined.");
            context.stop();
            return;
        }

        if (currentPeriod == null) {
            console.getErr().print("period not defined.");
            context.stop();
        }

        this.chart = context.getChart(currentInstrument);

        // currentInstrumentMQL = currentInstrument.name();
        currentPeriodMQL = (int) (currentPeriod.interval / 60000);

        if (currentPeriodMQL != PERIOD_D1 && currentPeriodMQL != PERIOD_H1 && currentPeriodMQL != PERIOD_H4 && currentPeriodMQL != PERIOD_M1 && currentPeriodMQL != PERIOD_M15 && currentPeriodMQL != PERIOD_M30 && currentPeriodMQL != PERIOD_M5 && currentPeriodMQL != PERIOD_MN1 && currentPeriodMQL != PERIOD_W1) {
            throw new JFException("period is not allowed.");
        }

        closeOrders.clear();

        /*
         * try { jFMailer = new JFMailer(); } catch (Throwable t) { //
         * t.printStackTrace(); }
         */

        box = new JFToolBox(context);

        alertDialog = new MQLAlertDialog(box, this.getClass().getSimpleName());

        init();
    }

    private double creditLine = 0;

    private double equity = 0;

    private double leverage = 0;

    private Currency baseCurrency = null;

    public void onAccount(IAccount account) throws JFException {
        log("onAccount(" + account + ")");
        creditLine = account.getCreditLine();
        equity = account.getEquity();
        leverage = account.getLeverage();
        baseCurrency = account.getCurrency();

    }

    private List<IOrder> closeOrders = new ArrayList<IOrder>();

    public void onMessage(IMessage message) throws JFException {

        // Print("onMessage ------------- " + message);
        log("onMessage(" + message + ")");
        if (message.getType() == IMessage.Type.ORDER_SUBMIT_OK) {

        }
        if (message.getType() == IMessage.Type.ORDER_FILL_OK) {

        }
        if (message.getType() == IMessage.Type.ORDER_CLOSE_OK) {
            IOrder order = message.getOrder();
            out: if (order != null) {
                for (IOrder co : closeOrders) {
                    if (order.getId() != null && order.getId().equals(co)) {
                        log("order already in list");
                        break out;
                    }
                }

                closeOrders.add(order);
            } else {
                log("onMessage - order is null on ORDER_CLOSE_OK");
            }
        }

    }

    private void log(String string) {
        if (mqldbg) {
            System.out.println("mqllog-" + string);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.dukascopy.api.IStrategy#onStop()
     */
    public void onStop() throws JFException {

        deinit();

        // close all file handlers
        for (RandomAccessFile accessFile : filesMap.values()) {
            if (accessFile != null) {
                try {
                    accessFile.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        // alertDialog.setVisible(false);
        filesMap.clear();
        filesDelimiters.clear();

    }

    private long LastTickTimeMQLFormat = 0;
    private long lastTickTime = 0;

    // private CurrConverter currConverter = new CurrConverter();

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if (currentInstrument != instrument) {
            return;
        }
        if (creditLine == 0) {
            log("waiting for account data.");
            return;
        }

        Ask = tick.getAsk();
        Bid = tick.getBid();
        Point = instrument.getPipValue();
        Digits = Point == 0.01 ? 3 : 5;
        lastTickTime = tick.getTime();
        LastTickTimeMQLFormat = lastTickTime / 1000;

        // Bars = //chart.getBarsCount();

        // Digits =
        start();

    }

    // ------------------------------------------------
    // MQL START

    // ------------------------------------------------

    protected int init() {
        return 0;
    };

    protected int deinit() {
        return 0;
    };

    abstract protected int start();

    // ------------------------------------------------
    // CONSTANTS
    // ------------------------------------------------

    // Timeframe of the chart (chart period). It can be any of the following
    // values:
    protected static final int PERIOD_M1 = 1;// 1 minute.

    protected static final int PERIOD_M5 = 5;// 5 minutes.

    protected static final int PERIOD_M15 = 15;// 15 minutes.

    protected static final int PERIOD_M30 = 30;// 30 minutes.

    protected static final int PERIOD_H1 = 60;// 1 hour.

    protected static final int PERIOD_H4 = 240;// 4 hour.

    protected static final int PERIOD_D1 = 1440;// Daily.

    protected static final int PERIOD_W1 = 10080;// Weekly.

    protected static final int PERIOD_MN1 = 43200;// Monthly.

    // 0 (zero) 0 Timeframe used on the chart.

    // Operation type for the OrderSend() function. It can be any of the
    // following values:
    protected static final int OP_BUY = 0;// Buying position.

    protected static final int OP_SELL = 1;// Selling position.

    protected static final int OP_BUYLIMIT = 2;// Buy limit pending position.

    protected static final int OP_SELLLIMIT = 3;// Sell limit pending position.

    protected static final int OP_BUYSTOP = 4;// Buy stop pending position.

    protected static final int OP_SELLSTOP = 5;// Sell stop pending position.

    protected static final int OP_BUYLIMIT_BIDOFFER = 6;// Buy limit pending
    // position.

    protected static final int OP_SELLLIMIT_BIDOFFER = 7;// Sell limit pending
    // position.

    // Applied price constants. It can be any of the following values:

    protected static final int PRICE_CLOSE = 0;// Close price.

    protected static final int PRICE_OPEN = 1;// Open price.

    protected static final int PRICE_HIGH = 2;// High price.

    protected static final int PRICE_LOW = 3;// Low price.

    protected static final int PRICE_MEDIAN = 4;// Median price, (high+low)/2.

    protected static final int PRICE_TYPICAL = 5;// Typical price,
    // (high+low+close)/3.

    protected static final int PRICE_WEIGHTED = 6;// Weighted close price,
    // (high+low+close+close)/4.

    // Series array identifier used with ArrayCopySeries(), iHighest() and
    // iLowest() functions.
    // It can be any of the following values:

    protected static final int MODE_OPEN = 0;// Open price.
    protected static final int MODE_CLOSE = 3;// Close price.
    protected static final int MODE_VOLUME = 4;// Volume, used in iLowest() and
    // iHighest() functions.

    // Market information identifiers, used with MarketInfo() function.
    // It can be any of the following values:
    protected static final int MODE_LOW = 1;// Low day price.

    protected static final int MODE_HIGH = 2;// High day price.

    protected static final int MODE_TIME = 5;// The last incoming tick time
    // (last known server time).

    protected static final int MODE_BID = 9;// Last incoming bid price. For the
    // current symbol, it is stored in
    // the predefined variable Bid

    protected static final int MODE_ASK = 10;// Last incoming ask price. For the
    // current symbol, it is stored
    // in the predefined variable
    // Ask

    protected static final int MODE_POINT = 11;// Point size in the quote
    // currency. For the current
    // symbol, it is stored in the
    // predefined variable Point

    protected static final int MODE_DIGITS = 12;// Count of digits after decimal
    // point in the symbol prices.
    // For the current symbol, it is
    // stored in the predefined
    // variable Digits

    protected static final int MODE_SPREAD = 13;// Spread value in points.

    protected static final int MODE_STOPLEVEL = 14;// Stop level in points.

    protected static final int MODE_LOTSIZE = 15;// Lot size in the base
    // currency.

    protected static final int MODE_TICKVALUE = 16;// Tick value in the deposit
    // currency.

    protected static final int MODE_TICKSIZE = 17;// Tick size in points.

    protected static final int MODE_SWAPLONG = 18;// Swap of the long position.

    protected static final int MODE_SWAPSHORT = 19;// Swap of the short
    // position.

    protected static final int MODE_STARTING = 20;// Market starting date
    // (usually used for
    // futures).

    protected static final int MODE_EXPIRATION = 21;// Market expiration date
    // (usually used for
    // futures).

    protected static final int MODE_TRADEALLOWED = 22;// Trade is allowed for
    // the symbol.

    protected static final int MODE_MINLOT = 23;// Minimum permitted amount of a
    // lot.

    protected static final int MODE_LOTSTEP = 24;// Step for changing lots.

    protected static final int MODE_MAXLOT = 25;// Maximum permitted amount of a
    // lot.

    protected static final int MODE_SWAPTYPE = 26;// Swap calculation method. 0
    // - in points; 1 - in the
    // symbol base currency; 2 -
    // by interest; 3 - in the
    // margin currency.

    protected static final int MODE_PROFITCALCMODE = 27;// Profit calculation
    // mode. 0 - Forex; 1 -
    // CFD; 2 - Futures.

    protected static final int MODE_MARGINCALCMODE = 28;// Margin calculation
    // mode. 0 - Forex; 1 -
    // CFD; 2 - Futures; 3 -
    // CFD for indices.

    protected static final int MODE_MARGININIT = 29;// Initial margin
    // requirements for 1 lot.

    protected static final int MODE_MARGINMAINTENANCE = 30;// Margin to maintain
    // open positions
    // calculated for 1
    // lot.

    protected static final int MODE_MARGINHEDGED = 31;// Hedged margin
    // calculated for 1 lot.

    protected static final int MODE_MARGINREQUIRED = 32;// Free margin required
    // to open 1 lot for
    // buying.

    protected static final int MODE_FREEZELEVEL = 33;// Order freeze level in
    // points. If the
    // execution price lies
    // within the range
    // defined by the freeze
    // level, the order
    // cannot be modified,
    // cancelled or closed.

    protected static final int MODE_ASCEND = 0;//   
    protected static final int MODE_DESCEND = 1;

    // COLORS
    protected Color AliceBlue = colorFromHex("F0F8FF");

    protected Color AntiqueWhite = colorFromHex("FAEBD7");

    protected Color Aqua = colorFromHex("00FFFF");

    protected Color Aquamarine = colorFromHex("7FFFD4");

    protected Color Azure = colorFromHex("F0FFFF");

    protected Color Beige = colorFromHex("F5F5DC");

    protected Color Bisque = colorFromHex("FFE4C4");

    protected Color Black = colorFromHex("000000");

    protected Color BlanchedAlmond = colorFromHex("FFEBCD");

    protected Color Blue = colorFromHex("0000FF");

    protected Color BlueViolet = colorFromHex("8A2BE2");

    protected Color Brown = colorFromHex("A52A2A");

    protected Color BurlyWood = colorFromHex("DEB887");

    protected Color CadetBlue = colorFromHex("5F9EA0");

    protected Color Chartreuse = colorFromHex("7FFF00");

    protected Color Chocolate = colorFromHex("D2691E");

    protected Color Coral = colorFromHex("FF7F50");

    protected Color CornflowerBlue = colorFromHex("6495ED");

    protected Color Cornsilk = colorFromHex("FFF8DC");

    protected Color Crimson = colorFromHex("DC143C");

    protected Color Cyan = colorFromHex("00FFFF");

    protected Color DarkBlue = colorFromHex("00008B");

    protected Color DarkCyan = colorFromHex("008B8B");

    protected Color DarkGoldenRod = colorFromHex("B8860B");

    protected Color DarkGray = colorFromHex("A9A9A9");

    protected Color DarkGreen = colorFromHex("006400");

    protected Color DarkKhaki = colorFromHex("BDB76B");

    protected Color DarkMagenta = colorFromHex("8B008B");

    protected Color DarkOliveGreen = colorFromHex("556B2F");

    protected Color DarkOrange = colorFromHex("FF8C00");

    protected Color DarkOrchid = colorFromHex("9932CC");

    protected Color DarkRed = colorFromHex("8B0000");

    protected Color DarkSalmon = colorFromHex("E9967A");

    protected Color DarkSeaGreen = colorFromHex("8FBC8F");

    protected Color DarkSlateBlue = colorFromHex("483D8B");

    protected Color DarkSlateGray = colorFromHex("2F4F4F");

    protected Color DarkSlateGrey = colorFromHex("2F4F4F");

    protected Color DarkTurquoise = colorFromHex("00CED1");

    protected Color DarkViolet = colorFromHex("9400D3");

    protected Color DeepPink = colorFromHex("FF1493");

    protected Color DeepSkyBlue = colorFromHex("00BFFF");

    protected Color DimGray = colorFromHex("696969");

    protected Color DimGrey = colorFromHex("696969");

    protected Color DodgerBlue = colorFromHex("1E90FF");

    protected Color FireBrick = colorFromHex("B22222");

    protected Color FloralWhite = colorFromHex("FFFAF0");

    protected Color ForestGreen = colorFromHex("228B22");

    protected Color Fuchsia = colorFromHex("FF00FF");

    protected Color Gainsboro = colorFromHex("DCDCDC");

    protected Color GhostWhite = colorFromHex("F8F8FF");

    protected Color Gold = colorFromHex("FFD700");

    protected Color GoldenRod = colorFromHex("DAA520");

    protected Color Gray = colorFromHex("808080");

    protected Color Grey = colorFromHex("808080");

    protected Color Green = colorFromHex("008000");

    protected Color GreenYellow = colorFromHex("ADFF2F");

    protected Color HoneyDew = colorFromHex("F0FFF0");

    protected Color HotPink = colorFromHex("FF69B4");

    protected Color IndianRed = colorFromHex("CD5C5C");

    protected Color Indigo = colorFromHex("4B0082");

    protected Color Ivory = colorFromHex("FFFFF0");

    protected Color Khaki = colorFromHex("F0E68C");

    protected Color Lavender = colorFromHex("E6E6FA");

    protected Color LavenderBlush = colorFromHex("FFF0F5");

    protected Color LawnGreen = colorFromHex("7CFC00");

    protected Color LemonChiffon = colorFromHex("FFFACD");

    protected Color LightBlue = colorFromHex("ADD8E6");

    protected Color LightCoral = colorFromHex("F08080");

    protected Color LightCyan = colorFromHex("E0FFFF");

    protected Color LightGoldenRodYellow = colorFromHex("FAFAD2");

    protected Color LightGray = colorFromHex("D3D3D3");

    protected Color LightGrey = colorFromHex("D3D3D3");

    protected Color LightGreen = colorFromHex("90EE90");

    protected Color LightPink = colorFromHex("FFB6C1");

    protected Color LightSalmon = colorFromHex("FFA07A");

    protected Color LightSeaGreen = colorFromHex("20B2AA");

    protected Color LightSkyBlue = colorFromHex("87CEFA");

    protected Color LightSlateGray = colorFromHex("778899");

    protected Color LightSlateGrey = colorFromHex("778899");

    protected Color LightSteelBlue = colorFromHex("B0C4DE");

    protected Color LightYellow = colorFromHex("FFFFE0");

    protected Color Lime = colorFromHex("00FF00");

    protected Color LimeGreen = colorFromHex("32CD32");

    protected Color Linen = colorFromHex("FAF0E6");

    protected Color Magenta = colorFromHex("FF00FF");

    protected Color Maroon = colorFromHex("800000");

    protected Color MediumAquaMarine = colorFromHex("66CDAA");

    protected Color MediumBlue = colorFromHex("0000CD");

    protected Color MediumOrchid = colorFromHex("BA55D3");

    protected Color MediumPurple = colorFromHex("9370D8");

    protected Color MediumSeaGreen = colorFromHex("3CB371");

    protected Color MediumSlateBlue = colorFromHex("7B68EE");

    protected Color MediumSpringGreen = colorFromHex("00FA9A");

    protected Color MediumTurquoise = colorFromHex("48D1CC");

    protected Color MediumVioletRed = colorFromHex("C71585");

    protected Color MidnightBlue = colorFromHex("191970");

    protected Color MintCream = colorFromHex("F5FFFA");

    protected Color MistyRose = colorFromHex("FFE4E1");

    protected Color Moccasin = colorFromHex("FFE4B5");

    protected Color NavajoWhite = colorFromHex("FFDEAD");

    protected Color Navy = colorFromHex("000080");

    protected Color OldLace = colorFromHex("FDF5E6");

    protected Color Olive = colorFromHex("808000");

    protected Color OliveDrab = colorFromHex("6B8E23");

    protected Color Orange = colorFromHex("FFA500");

    protected Color OrangeRed = colorFromHex("FF4500");

    protected Color Orchid = colorFromHex("DA70D6");

    protected Color PaleGoldenRod = colorFromHex("EEE8AA");

    protected Color PaleGreen = colorFromHex("98FB98");

    protected Color PaleTurquoise = colorFromHex("AFEEEE");

    protected Color PaleVioletRed = colorFromHex("D87093");

    protected Color PapayaWhip = colorFromHex("FFEFD5");

    protected Color PeachPuff = colorFromHex("FFDAB9");

    protected Color Peru = colorFromHex("CD853F");

    protected Color Pink = colorFromHex("FFC0CB");

    protected Color Plum = colorFromHex("DDA0DD");

    protected Color PowderBlue = colorFromHex("B0E0E6");

    protected Color Purple = colorFromHex("800080");

    protected Color Red = colorFromHex("FF0000");

    protected Color RosyBrown = colorFromHex("BC8F8F");

    protected Color RoyalBlue = colorFromHex("4169E1");

    protected Color SaddleBrown = colorFromHex("8B4513");

    protected Color Salmon = colorFromHex("FA8072");

    protected Color SandyBrown = colorFromHex("F4A460");

    protected Color SeaGreen = colorFromHex("2E8B57");

    protected Color SeaShell = colorFromHex("FFF5EE");

    protected Color Sienna = colorFromHex("A0522D");

    protected Color Silver = colorFromHex("C0C0C0");

    protected Color SkyBlue = colorFromHex("87CEEB");

    protected Color SlateBlue = colorFromHex("6A5ACD");

    protected Color SlateGray = colorFromHex("708090");

    protected Color SlateGrey = colorFromHex("708090");

    protected Color Snow = colorFromHex("FFFAFA");

    protected Color SpringGreen = colorFromHex("00FF7F");

    protected Color SteelBlue = colorFromHex("4682B4");

    protected Color Tan = colorFromHex("D2B48C");

    protected Color Teal = colorFromHex("008080");

    protected Color Thistle = colorFromHex("D8BFD8");

    protected Color Tomato = colorFromHex("FF6347");

    protected Color Turquoise = colorFromHex("40E0D0");

    protected Color Violet = colorFromHex("EE82EE");

    protected Color Wheat = colorFromHex("F5DEB3");

    protected Color White = colorFromHex("FFFFFF");

    protected Color WhiteSmoke = colorFromHex("F5F5F5");

    protected Color Yellow = colorFromHex("FFFF00");

    protected Color YellowGreen = colorFromHex("9ACD32");

    private Color colorFromHex(String hex) {
        int intValue = Integer.parseInt(hex, 16);
        Color color = new Color(intValue);
        return color;
    }

    // 

    protected static final int OBJPROP_TIME1 = 0;// datetime Datetime value to
    // set/get first coordinate
    // time part.
    protected static final int OBJPROP_PRICE1 = 1;// double Double value to
    // set/get first coordinate
    // price part.
    protected static final int OBJPROP_TIME2 = 2;// datetime Datetime value to
    // set/get second coordinate
    // time part.
    protected static final int OBJPROP_PRICE2 = 3;// double Double value to
    // set/get second coordinate
    // price part.
    protected static final int OBJPROP_TIME3 = 4;// datetime Datetime value to
    // set/get third coordinate
    // time part.
    protected static final int OBJPROP_PRICE3 = 5;// double Double value to
    // set/get third coordinate
    // price part.
    protected static final int OBJPROP_COLOR = 6;// color Color value to set/get
    // object color.
    protected static final int OBJPROP_STYLE = 7;// int Value is one of
    // STYLE_SOLID, STYLE_DASH,
    // STYLE_DOT, STYLE_DASHDOT,
    // STYLE_DASHDOTDOT constants
    // to set/get object line
    // style.
    protected static final int OBJPROP_WIDTH = 8;// int Integer value to set/get
    // object line width. Can be
    // from 1 to 5.
    protected static final int OBJPROP_BACK = 9;// bool Boolean value to set/get
    // background drawing flag for
    // object.
    protected static final int OBJPROP_RAY = 10;// bool Boolean value to set/get
    // ray flag of object.
    protected static final int OBJPROP_ELLIPSE = 11;// bool Boolean value to
    // set/get ellipse flag for
    // fibo arcs.
    protected static final int OBJPROP_SCALE = 12;// double Double value to
    // set/get scale object
    // property.
    protected static final int OBJPROP_ANGLE = 13;// double Double value to
    // set/get angle object
    // property in degrees.
    protected static final int OBJPROP_ARROWCODE = 14;// int Integer value or
    // arrow enumeration to
    // set/get arrow code
    // object property.
    protected static final int OBJPROP_TIMEFRAMES = 15;// int Value can be one
    // or combination
    // (bitwise addition) of
    // object visibility
    // constants to set/get
    // timeframe object
    // property.
    protected static final int OBJPROP_DEVIATION = 16;// double Double value to
    // set/get deviation
    // property for Standard
    // deviation objects.
    protected static final int OBJPROP_FONTSIZE = 100;// int Integer value to
    // set/get font size for
    // text objects.
    protected static final int OBJPROP_CORNER = 101;// int Integer value to
    // set/get anchor corner
    // property for label
    // objects. Must be from
    // 0-3.
    protected static final int OBJPROP_XDISTANCE = 102;// int Integer value to
    // set/get anchor X
    // distance object
    // property in pixels.
    protected static final int OBJPROP_YDISTANCE = 103;// int Integer value is
    // to set/get anchor Y
    // distance object
    // property in pixels.
    protected static final int OBJPROP_FIBOLEVELS = 200;// int Integer value to
    // set/get Fibonacci
    // object level count.
    // Can be from 0 to 32.
    protected static final int OBJPROP_LEVELCOLOR = 201;// color Color value to
    // set/get object level
    // line color.
    protected static final int OBJPROP_LEVELSTYLE = 202;// int Value is one of
    // STYLE_SOLID,
    // STYLE_DASH,
    // STYLE_DOT,
    // STYLE_DASHDOT,
    // STYLE_DASHDOTDOT
    // constants to set/get
    // object level line
    // style.
    protected static final int OBJPROP_LEVELWIDTH = 203;// int Integer value to

    // set/get object level
    // line width. Can be
    // from 1 to 5.

    // protected static final int OBJPROP_FIRSTLEVEL+n 210+n int Integer value
    // to set/get the value of Fibonacci object level with index n. Index n can
    // be from 0 (number of levels -1), but not larger than 31.

    protected IChartObject.ATTR_BOOLEAN getObjectPropBoolean(int index) {
        switch (index) {
        case OBJPROP_BACK:
            return IChartObject.ATTR_BOOLEAN.BACK;
        case OBJPROP_ELLIPSE:
            return IChartObject.ATTR_BOOLEAN.ELLIPSE;
        case OBJPROP_RAY:
            return IChartObject.ATTR_BOOLEAN.RAY;
        default:
            return null;
        }
    }

    protected IChartObject.ATTR_COLOR getObjectPropColor(int index) {
        switch (index) {
        case OBJPROP_COLOR:
            return IChartObject.ATTR_COLOR.COLOR;
        default:
            return null;
        }
    }

    protected IChartObject.ATTR_DOUBLE getObjectPropDouble(int index) {
        switch (index) {
        case OBJPROP_ANGLE:
            return IChartObject.ATTR_DOUBLE.ANGLE;
        case OBJPROP_DEVIATION:
            return IChartObject.ATTR_DOUBLE.DEVIATION;
        case OBJPROP_PRICE1:
            return IChartObject.ATTR_DOUBLE.PRICE1;
        case OBJPROP_PRICE2:
            return IChartObject.ATTR_DOUBLE.PRICE2;
        case OBJPROP_PRICE3:
            return IChartObject.ATTR_DOUBLE.PRICE3;
        case OBJPROP_SCALE:
            return IChartObject.ATTR_DOUBLE.SCALE;
        default:
            return null;
        }
    }

    protected IChartObject.ATTR_INT getObjectPropInt(int index) {
        switch (index) {
        case OBJPROP_ARROWCODE:
            return IChartObject.ATTR_INT.ARROWCODE;
        case OBJPROP_CORNER:
            return IChartObject.ATTR_INT.CORNER;
        case OBJPROP_FONTSIZE:
            return IChartObject.ATTR_INT.FONTSIZE;
        case OBJPROP_LEVELSTYLE:
            return IChartObject.ATTR_INT.LEVELSTYLE;
        case OBJPROP_LEVELWIDTH:
            return IChartObject.ATTR_INT.LEVELWIDTH;
        case OBJPROP_STYLE:
            return IChartObject.ATTR_INT.STYLE;
        case OBJPROP_TIMEFRAMES:
            return IChartObject.ATTR_INT.TIMEFRAMES;
        case OBJPROP_WIDTH:
            return IChartObject.ATTR_INT.WIDTH;
        case OBJPROP_XDISTANCE:
            return IChartObject.ATTR_INT.XDISTANCE;
        case OBJPROP_YDISTANCE:
            return IChartObject.ATTR_INT.YDISTANCE;

        default:
            return null;
        }
    }

    protected IChartObject.ATTR_LONG getObjectPropLong(int index) {
        switch (index) {
        case OBJPROP_TIME1:
            return IChartObject.ATTR_LONG.TIME1;
        case OBJPROP_TIME2:
            return IChartObject.ATTR_LONG.TIME2;
        case OBJPROP_TIME3:
            return IChartObject.ATTR_LONG.TIME3;
        default:
            return null;
        }
    }

    // Indicator line identifiers used in iMACD(), iRVI() and iStochastic()
    // indicators.
    // It can be one of the following values:

    protected static final int MODE_MAIN = 0;// Base indicator line.

    protected static final int MODE_SIGNAL = 1;// Signal line.

    // Indicator line identifiers used in iADX() indicator.

    protected static final int MODE_PLUSDI = 1;// +DI indicator line.

    protected static final int MODE_MINUSDI = 2;// -DI indicator line.

    // Indicator line identifiers used in iBands(), iEnvelopes(),
    // iEnvelopesOnArray(), iFractals() and iGator() indicators.
    protected static final int MODE_UPPER = 1;// Upper line.

    protected static final int MODE_LOWER = 2;// Lower line.

    // Moving Average calculation method used with iAlligator(), iEnvelopes(),
    // iEnvelopesOnArray, iForce(), iGator(), iMA(), iMAOnArray(), iStdDev(),
    // iStdDevOnArray(), iStochastic() indicators.
    // It can be any of the following values:

    protected static final int MODE_SMA = 0;// Simple moving average,

    protected static final int MODE_EMA = 1;// Exponential moving average,

    protected static final int MODE_SMMA = 2;// Smoothed moving average,

    protected static final int MODE_LWMA = 3;// Linear weighted moving average.

    /*
     * To indicate the buttons displayed in the message box, specify one of the
     * following values.
     * 
     * Constant Value Description
     */
    protected static final int MB_OK = 0x00000000;// The message box contains
    // one push button: OK. This
    // is the default.

    protected static final int MB_OKCANCEL = 0x00000001; // The message box
    // contains two push
    // buttons: OK and
    // Cancel.

    protected static final int MB_ABORTRETRYIGNORE = 0x00000002; // The message
    // box
    // contains
    // three
    // push
    // buttons:
    // Abort,
    // Retry,
    // and
    // Ignore.

    protected static final int MB_YESNOCANCEL = 0x00000003; // The message box
    // contains three
    // push buttons:
    // Yes, No, and
    // Cancel.

    protected static final int MB_YESNO = 0x00000004; // The message box
    // contains two push
    // buttons: Yes and No.

    protected static final int MB_RETRYCANCEL = 0x00000005; // The message box
    // contains two push
    // buttons: Retry
    // and Cancel.

    protected static final int MB_CANCELTRYCONTINUE = 0x00000006; // Windows
    // 2000: The
    // message
    // box
    // contains
    // three
    // push
    // buttons:
    // Cancel,
    // Try
    // Again,
    // Continue.
    // Use this
    // message
    // box type
    // instead
    // of
    // MB_ABORTRETRYIGNORE.

    protected static final int IDOK = 1;// OK button was selected.

    protected static final int IDCANCEL = 2; // Cancel button was selected.

    protected static final int IDABORT = 3; // Abort button was selected.

    protected static final int IDRETRY = 4; // Retry button was selected.

    protected static final int IDIGNORE = 5; // Ignore button was selected.

    protected static final int IDYES = 6; // Yes button was selected.

    protected static final int IDNO = 7; // No button was selected.

    protected static final int IDTRYAGAIN = 10; // Try Again button was
    // selected.

    protected static final int IDCONTINUE = 11;// Continue button was selected.

    // =ERRORS

    protected static final int ERR_NO_RESULT = 1;// No error returned, but the
    // result is unknown.

    protected static final int ERR_COMMON_ERROR = 2;// Common error.

    protected static final int ERR_INVALID_TRADE_PARAMETERS = 3;// Invalid trade
    // parameters.

    protected static final int ERR_SERVER_BUSY = 4;// Trade server is busy.

    protected static final int ERR_OLD_VERSION = 5;// Old version of the client
    // terminal.

    protected static final int ERR_NO_CONNECTION = 6;// No connection with trade
    // server.

    protected static final int ERR_NOT_ENOUGH_RIGHTS = 7;// Not enough rights.

    protected static final int ERR_TOO_FREQUENT_REQUESTS = 8;// Too frequent
    // requests.

    protected static final int ERR_MALFUNCTIONAL_TRADE = 9;// Malfunctional
    // trade operation.

    protected static final int ERR_ACCOUNT_DISABLED = 64;// Account disabled.

    protected static final int ERR_INVALID_ACCOUNT = 65;// Invalid account.

    protected static final int ERR_TRADE_TIMEOUT = 128;// Trade timeout.

    protected static final int ERR_INVALID_PRICE = 129;// Invalid price.

    protected static final int ERR_INVALID_STOPS = 130;// Invalid stops.

    protected static final int ERR_INVALID_TRADE_VOLUME = 131;// Invalid trade
    // volume.

    protected static final int ERR_MARKET_CLOSED = 132;// Market is closed.

    protected static final int ERR_TRADE_DISABLED = 133;// Trade is disabled.

    protected static final int ERR_NOT_ENOUGH_MONEY = 134;// Not enough money.

    protected static final int ERR_PRICE_CHANGED = 135;// Price changed.

    protected static final int ERR_OFF_QUOTES = 136;// Off quotes.

    protected static final int ERR_BROKER_BUSY = 137;// Broker is busy.

    protected static final int ERR_REQUOTE = 138;// Requote.

    protected static final int ERR_ORDER_LOCKED = 139;// Order is locked.

    protected static final int ERR_LONG_POSITIONS_ONLY_ALLOWED = 140;// Long
    // positions
    // only
    // allowed.

    protected static final int ERR_TOO_MANY_REQUESTS = 141;// Too many requests.

    protected static final int ERR_TRADE_MODIFY_DENIED = 145;// Modification
    // denied
    // because order
    // too close to
    // market.

    protected static final int ERR_TRADE_CONTEXT_BUSY = 146;// Trade context is
    // busy.

    protected static final int ERR_TRADE_EXPIRATION_DENIED = 147;// Expirations
    // are
    // denied by
    // broker.

    protected static final int ERR_TRADE_TOO_MANY_ORDERS = 148;// The amount of
    // open and
    // pending
    // orders has
    // reached the
    // limit set by
    // the broker.

    // Object type identifier constants used with ObjectCreate(),
    // ObjectsDeleteAll() and ObjectType() functions. It can be any of the
    // following values:
    // Objects can have 1-3 coordinates related to type.

    protected static final int OBJ_VLINE = 0; // Vertical line. Uses time part
    // of first coordinate.

    protected static final int OBJ_HLINE = 1; // Horizontal line. Uses price
    // part of first coordinate.

    protected static final int OBJ_TREND = 2; // Trend line. Uses 2 coordinates.

    protected static final int OBJ_TRENDBYANGLE = 3; // Trend by angle. Uses 1
    // coordinate. To set
    // angle of line use
    // ObjectSet() function.

    protected static final int OBJ_REGRESSION = 4; // Regression. Uses time
    // parts of first two
    // coordinates.

    protected static final int OBJ_CHANNEL = 5; // Channel. Uses 3 coordinates.

    protected static final int OBJ_STDDEVCHANNEL = 6; // Standard deviation
    // channel. Uses time
    // parts of first two
    // coordinates.

    protected static final int OBJ_GANNLINE = 7; // Gann line. Uses 2
    // coordinate, but price
    // part of second coordinate
    // ignored.

    protected static final int OBJ_GANNFAN = 8; // Gann fan. Uses 2 coordinate,
    // but price part of second
    // coordinate ignored.

    protected static final int OBJ_GANNGRID = 9; // Gann grid. Uses 2
    // coordinate, but price
    // part of second coordinate
    // ignored.

    protected static final int OBJ_FIBO = 10; // Fibonacci retracement. Uses 2
    // coordinates.

    protected static final int OBJ_FIBOTIMES = 11; // Fibonacci time zones. Uses
    // 2 coordinates.

    protected static final int OBJ_FIBOFAN = 12; // Fibonacci fan. Uses 2
    // coordinates.

    protected static final int OBJ_FIBOARC = 13; // Fibonacci arcs. Uses 2
    // coordinates.

    protected static final int OBJ_EXPANSION = 14; // Fibonacci expansions. Uses
    // 3 coordinates.

    protected static final int OBJ_FIBOCHANNEL = 15; // Fibonacci channel. Uses
    // 3 coordinates.

    protected static final int OBJ_RECTANGLE = 16; // Rectangle. Uses 2
    // coordinates.

    protected static final int OBJ_TRIANGLE = 17; // Triangle. Uses 3
    // coordinates.

    protected static final int OBJ_ELLIPSE = 18; // Ellipse. Uses 2 coordinates.

    protected static final int OBJ_PITCHFORK = 19; // Andrews pitchfork. Uses 3
    // coordinates.

    protected static final int OBJ_CYCLES = 20; // Cycles. Uses 2 coordinates.

    protected static final int OBJ_TEXT = 21; // Text. Uses 1 coordinate.

    protected static final int OBJ_ARROW = 22; // Arrows. Uses 1 coordinate.

    protected static final int OBJ_LABEL = 23; // Text label. Uses 1 coordinate

    // in pixels.

    // ------------------------------------------------
    // String functions
    // ------------------------------------------------

    /**
     * Extracts a substring from text string starting from the given position.
     * The function returns a copy of the extracted substring if possible,
     * otherwise, it returns an empty string.
     */

    protected String StringSubstr(String text, int start, int length) {
        return text.substring(start, start + length);
    }

    /**
     * Forms a string of the data passed and returns it. Parameters can be of
     * any type. Amount of passed parameters cannot exceed 64.
     * 
     * Parameters are transformed into strings according the same rules as those
     * used in functions of Print(), Alert() and Comment(). The returned string
     * is obtained as a result of concatenate of strings converted from the
     * function parameters.
     * 
     * The StringConcatenate() works faster and more memory-saving than when
     * strings are concatenated using addition operations (+).
     */
    protected String StringConcatenate(Object... str) {
        return box.multiargToString("", str);
    }

    /**
     * Search for a substring. Returns the position in the string from which the
     * searched substring begins, or -1 if the substring has not been found.
     */
    protected int StringFind(String text, String matched_text) {
        return StringFind(text, matched_text, 0);
    }

    protected int StringFind(String text, String matched_text, int start) {
        int rc = -1;
        if (text != null && matched_text != null) {
            rc = text.indexOf(matched_text, start);
        }
        return rc;
    }

    /**
     * Returns character count in a string.
     */
    protected int StringLen(String text) {
        return text != null ? text.length() : 0;
    }

    /**
     * Returns character (code) from the specified position in the string.
     */
    protected int StringGetChar(String text, int pos) {
        return text.charAt(pos);
    }

    // ------------------------------------------------
    // predefined variables
    // ------------------------------------------------

    /*
     * The latest known seller's price (ask price) for the current symbol. The
     * RefreshRates() function must be used to update. See also MarketInfo().
     */
    protected double Ask = 0;

    /*
     * Number of bars in the current chart. See also iBars().
     */
    protected int Bars__ = 0;

    /*
     * The latest known buyer's price (offer price, bid price) of the current
     * symbol. The RefreshRates() function must be used to update. See also
     * MarketInfo().
     */
    protected double Bid = 0;

    /*
     * Series array that contains close prices for each bar of the current
     * chart.
     * 
     * Series array elements are indexed in the reverse order, i.e., from the
     * last one to the first one. The current bar which is the last in the array
     * is indexed as 0. The oldest bar, the first in the chart, is indexed as
     * Bars-1.
     * 
     * See also iClose().
     */
    protected double[] Close__;

    /*
     * Number of digits after decimal point for the current symbol prices. See
     * also MarketInfo().
     */
    protected int Digits;

    /*
     * Series array that contains the highest prices of each bar of the current
     * chart.
     * 
     * Series array elements are indexed in the reverse order, i.e., from the
     * last one to the first one. The current bar which is the last in the array
     * is indexed as 0. The oldest bar, the first in the chart, is indexed as
     * Bars-1.
     * 
     * See also iHigh().
     */

    protected double[] High__;

    /*
     * Series array that contains the lowest prices of each bar of the current
     * chart.
     * 
     * Series array elements are indexed in the reverse order, i.e., from the
     * last one to the first one. The current bar which is the last in the array
     * is indexed as 0. The oldest bar, the first in the chart, is indexed as
     * Bars-1.
     * 
     * See also iLow().
     */

    protected double[] Low__;

    /*
     * Series array that contains open prices of each bar of the current chart.
     * 
     * Series array elements are indexed in the reverse order, i.e., from the
     * last one to the first one. The current bar which is the last in the array
     * is indexed as 0. The oldest bar, the first in the chart, is indexed as
     * Bars-1.
     * 
     * See also iOpen().
     */

    protected double[] Open__;

    /*
     * The current symbol point value in the quote currency. See also
     * MarketInfo().
     */
    protected double Point;

    /*
     * Series array that contains open time of each bar of the current chart.
     * Data like datetime represent time, in seconds, that has passed since
     * 00:00 a.m. of 1 January, 1970.
     * 
     * Series array elements are indexed in the reverse order, i.e., from the
     * last one to the first one. The current bar which is the last in the array
     * is indexed as 0. The oldest bar, the first in the chart, is indexed as
     * Bars-1.
     * 
     * See also iTime().
     */
    protected long[] Time__;

    /*
     * Series array that contains tick volumes of each bar of the current chart.
     * 
     * Series array elements are indexed in the reverse order, i.e., from the
     * last one to the first one. The current bar which is the last in the array
     * is indexed as 0. The oldest bar, the first in the chart, is indexed as
     * Bars-1.
     * 
     * See also iVolume().
     */
    protected double[] Volume__;

    // -----------------------------------------------------------------------------------------------
    // Timeseries access
    // -----------------------------------------------------------------------------------------------

    // replacement for predefined array Close[]

    protected double Close(int shift, OfferSide offerSide) {
        return iClose(currentInstrument, currentPeriodMQL, shift, offerSide);
    }

    protected double Close(int shift) {
        return iClose(currentInstrument, currentPeriodMQL, shift);
    }

    /*
     * Returns Close value for the bar of indicated symbol with timeframe and
     * shift. If local history is empty (not loaded), function returns 0. For
     * the current chart, the information about close prices is in the
     * predefined array named Close[].
     */

    protected double iClose(Instrument symbol, int timeframe, int shift) {
        return iClose(symbol, timeframe, shift, OfferSide.BID);
    }

    protected double iClose(Instrument symbol, int timeframe, int shift, OfferSide offerSide) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);

        IBar bar = null;
        try {
            bar = history.getBar(symbol, convertedPeriod, offerSide, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        if (bar != null) {
            rc = bar.getClose();
        }
        return rc;

    }

    /*
     * Returns High value for the bar of indicated symbol with timeframe and
     * shift. If local history is empty (not loaded), function returns 0. For
     * the current chart, the information about high prices is in the predefined
     * array named High[].
     */
    protected double High(int shift, OfferSide offerSide) {
        return iHigh(currentInstrument, currentPeriodMQL, shift, offerSide);
    }

    protected double High(int shift) {
        return iHigh(currentInstrument, currentPeriodMQL, shift);
    }

    protected double iHigh(Instrument symbol, int timeframe, int shift) {
        return iHigh(symbol, timeframe, shift, OfferSide.BID);
    }

    protected double iHigh(Instrument symbol, int timeframe, int shift, OfferSide offerSide) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);
        IBar bar = null;
        try {
            bar = history.getBar(symbol, convertedPeriod, offerSide, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        if (bar != null) {
            rc = bar.getHigh();
        }
        return rc;
    }

    /*
     * Returns Low value for the bar of indicated symbol with timeframe and
     * shift. If local history is empty (not loaded), function returns 0. For
     * the current chart, the information about low prices is in the predefined
     * array named Low[].
     */
    protected double Low(int shift, OfferSide offerSide) {
        return iLow(currentInstrument, currentPeriodMQL, shift, offerSide);
    }

    protected double Low(int shift) {
        return iLow(currentInstrument, currentPeriodMQL, shift);
    }

    protected double iLow(Instrument symbol, int timeframe, int shift) {
        return iLow(symbol, timeframe, shift, OfferSide.BID);
    }

    protected double iLow(Instrument symbol, int timeframe, int shift, OfferSide offerSide) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);
        IBar bar = null;
        try {
            bar = history.getBar(symbol, convertedPeriod, offerSide, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        if (bar != null) {
            rc = bar.getLow();
        }
        return rc;
    }

    protected double Open(int shift) {
        return iOpen(currentInstrument, currentPeriodMQL, shift);
    }

    protected double Open(int shift, OfferSide offerSide) {
        return iOpen(currentInstrument, currentPeriodMQL, shift, offerSide);
    }

    protected double iOpen(Instrument symbol, int timeframe, int shift) {
        return iOpen(symbol, timeframe, shift, OfferSide.BID);
    }

    protected double iOpen(Instrument symbol, int timeframe, int shift, OfferSide offerSide) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);
        IBar bar = null;
        try {
            bar = history.getBar(symbol, convertedPeriod, offerSide, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        if (bar != null) {
            rc = bar.getOpen();
        }
        return rc;
    }

    // replacement for predefined array Time[]
    protected long Time(int shift) {
        return iTime(currentInstrument, currentPeriodMQL, shift);
    }

    /*
     * Returns Open value for the bar of indicated symbol with timeframe and
     * shift. If local history is empty (not loaded), function returns 0. For
     * the current chart, the information about open prices is in the predefined
     * array named Open[].
     */

    protected long iTime(Instrument symbol, int timeframe, int shift) {
        long rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);

        IBar bar = null;
        try {
            bar = history.getBar(symbol, convertedPeriod, OfferSide.BID, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        if (bar != null) {
            rc = bar.getTime() / 1000;
        }
        return rc;
    }

    /*
     * Returns Time value for the bar of indicated symbol with timeframe and
     * shift. If local history is empty (not loaded), function returns 0. For
     * the current chart, the information about bars open times is in the
     * predefined array named Time[].
     */

    protected double iVolume(Instrument symbol, int timeframe, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);

        IBar bar = null;
        try {
            bar = history.getBar(symbol, convertedPeriod, OfferSide.BID, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        if (bar != null) {
            rc = bar.getVolume();
        }
        return rc;

    }

    /*
     * Returns the shift of the least value over a specific number of periods
     * depending on type.
     */
    protected int iLowest(Instrument symbol, int timeframe, int type, int count/**/, int start/*
                                                                                                * =0
                                                                                                */) {
        return iLowest(symbol, timeframe, type, count, start, OfferSide.BID);
    }

    protected int iLowest(Instrument symbol, int timeframe, int type, int count/**/, int start/*
                                                                                                * =0
                                                                                                */, OfferSide offerSide) {
        int rc = -1;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);

        try {

            IBar[] bars = box.getNBars(symbol, convertedPeriod, offerSide, start, count);
            double lowValue = Double.MAX_VALUE;
            for (int i = 0; i < bars.length; i++) {

                IBar bar = bars[i];

                double value = 0;
                switch (type) {
                case MODE_OPEN:
                    value = bar.getOpen();
                    break;
                case MODE_LOW:
                    value = bar.getLow();
                    break;
                case MODE_HIGH:
                    value = bar.getHigh();
                    break;
                case MODE_CLOSE:
                    value = bar.getClose();
                    break;
                case MODE_VOLUME:
                    value = bar.getVolume();
                    break;
                case MODE_TIME:
                    value = bar.getTime() / 1000;
                    break;
                default:
                    value = bar.getOpen();
                    break;
                }
                if (value < lowValue) {
                    rc = i + start;
                    lowValue = value;
                }
            }

        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;

    }

    /*
     * Returns the shift of the maximum value over a specific number of periods
     * depending on type.
     */
    protected int iHighest(Instrument symbol, int timeframe, int type, int count, int start) {
        return iHighest(symbol, timeframe, type, count, start, OfferSide.BID);
    }

    protected int iHighest(Instrument symbol, int timeframe, int type, int count, int start, OfferSide offerSide) {
        int rc = -1;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);

        try {

            IBar[] bars = box.getNBars(symbol, convertedPeriod, offerSide, start, count);
            double highValue = Double.MIN_VALUE;
            for (int i = 0; i < bars.length; i++) {

                IBar bar = bars[i];

                double value = 0;
                switch (type) {
                case MODE_OPEN:
                    value = bar.getOpen();
                    break;
                case MODE_LOW:
                    value = bar.getLow();
                    break;
                case MODE_HIGH:
                    value = bar.getHigh();
                    break;
                case MODE_CLOSE:
                    value = bar.getClose();
                    break;
                case MODE_VOLUME:
                    value = bar.getVolume();
                    break;
                case MODE_TIME:
                    value = bar.getTime() / 1000;
                    break;
                default:
                    value = bar.getOpen();
                    break;
                }
                if (value > highValue) {
                    rc = i + start;
                    highValue = value;
                }
            }

        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;
    }

    /**
     * Search for bar by open time. The function returns bar shift with the open
     * time specified. If the bar having the specified open time is missing, the
     * function will return -1 or the nearest bar shift depending on the exact.
     * 
     * Thanks to Michel for contributing this improved method (2009-08-19)
     * 
     */
    protected int iBarShift(Instrument symbol, int timeframe, long time, boolean exact) {
        int indexer = 0;
        if (symbol == null)
            symbol = currentInstrument;
        Period convertedPeriod = convertPeriod(timeframe);
        try {
            IBar bar = null;
            while (true) {
                bar = history.getBar(symbol, convertedPeriod, OfferSide.BID, indexer);
                if (bar == null || bar.getTime() <= (time * 1000))
                    break;
                indexer++;
            }
            if (bar == null && indexer > 0) { // back to the previous bar
                indexer--;
                bar = history.getBar(symbol, convertedPeriod, OfferSide.BID, indexer);
            }
            if (bar == null)
                return (-1);
            if (exact) {
                if (bar.getTime() == time * 1000)
                    return (indexer);
                return (-1);
            }
            return (indexer);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return (-1);
    }

    /*
     * Returns Tick Volume value for the bar of indicated symbol with timeframe
     * and shift. If local history is empty (not loaded), function returns 0.
     * For the current chart, the information about bars tick volumes is in the
     * predefined array named Volume[].
     */

    // -----------------------------------------------------------------------------------------------
    // Common functions
    // -----------------------------------------------------------------------------------------------
    // General-purpose functions not included into any specialized groups.
    /**
     * Sends a message to the e-mail set in the Tools->Options->EMail tab. The
     * sending can be disabled in settings, or it can be omitted to specify the
     * e-mail address. To get the detailed error information, one has to call
     * the GetLastError() functio
     */
    protected void SendMail(String subject, String some_text) {
        // if (jFMailer != null) {
        // jFMailer.sendMail(subject, some_text);
        // } else {
        String msg = "Mail system unavailable. Trying to send an email:[subj:" + subject + "][text:" + some_text + "]";
        if (console != null) {
            console.getErr().println(msg);
        } else {
            System.out.println(msg);
        }
        // }

    }

    /*
     * The MessageBox function creates, displays, and operates message box. The
     * message box contains an application-defined message and header, as well
     * as a random combination of predefined icons and push buttons. If the
     * function succeeds, the returned value is one of the MessageBox return
     * code values.
     * 
     * The function cannot be called from custom indicators since they are
     * executed within interface thread and may not decelerate it.
     */

    protected int MessageBox(String message) {
        return MessageBox(message, "Message");
    }

    protected int MessageBox(String message, String title) {
        return MessageBox(message, title, MB_OK);
    }

    protected int MessageBox(String message, String title, int flags) {

        int rc = -1;
        int optionType = JOptionPane.DEFAULT_OPTION;

        switch (flags) {
        case MB_OK:
            optionType = JOptionPane.DEFAULT_OPTION;
            break;
        case MB_OKCANCEL:
            optionType = JOptionPane.OK_CANCEL_OPTION;
            break;
        case MB_YESNO:
            optionType = JOptionPane.YES_NO_OPTION;
            break;
        case MB_YESNOCANCEL:
            optionType = JOptionPane.YES_NO_CANCEL_OPTION;
            break;

        default:
            notSupported();
            break;
        }

        JOptionPane pane = new JOptionPane(message, JOptionPane.INFORMATION_MESSAGE, optionType);
        // pane.set.Xxxx(...); // Configure
        JDialog dialog = pane.createDialog(null, title);
        dialog.setVisible(true);
        Object selectedValue = pane.getValue();
        if (selectedValue == null) {
            return rc;
        }
        // If there is not an array of option buttons:

        if (selectedValue instanceof Integer) {
            rc = ((Integer) selectedValue).intValue();
            if (rc == JOptionPane.CLOSED_OPTION) {
                rc = 0;
            } else if (rc == JOptionPane.YES_OPTION) {
                rc = IDYES;
            } else if (rc == JOptionPane.NO_OPTION) {
                rc = IDNO;
            } else if (rc == JOptionPane.CANCEL_OPTION) {
                rc = IDCANCEL;
            } else if (rc == JOptionPane.OK_OPTION) {
                rc = IDOK;
            } else {
                notSupported();
            }

        }
        return rc;

    }

    /**
     * Displays a dialog box containing the user-defined data. Parameters can be
     * of any type. Amount of passed parameters cannot exceed 64.
     * 
     * Arrays cannot be passed to the Alert function. Arrays should be output
     * elementwise.
     * 
     * Data of double type output with 4 decimal digits after point. To output
     * with more precision use DoubleToStr() function. Data of bool, datetime
     * and color types will be output as its numeric presentation. To output
     * values of datetime type as string convert it by TimeToStr() function. See
     * also Comment() and Print() functions. Parameters: ... - Any values,
     * separated by commas. It can be up to 64 parameters. Sample:
     * 
     * if(Close[0]>SignalLevel) Alert("Close price coming ", Close[0],"!!!");
     * 
     * Common functions Comment
     * 
     * @throws JFException
     */

    protected void Alert(Object... str) {
        // JOptionPane.showMessageDialog(null, box.multiargToString("", str));
        if (engine.getType() != IEngine.Type.TEST) {
            alertDialog.show(lastTickTime, box.multiargToString("", str));
        }
    }

    /*
     * The function outputs the comment defined by the user in the left top
     * corner of the chart. Parameters can be of any type. Amount of passed
     * parameters cannot exceed 64.
     * 
     * Arrays cannot be passed to the Comment() function. Arrays should be
     * output elementwise.
     * 
     * Data of double type output with 4 digits after the decimal point. To
     * output with more precision, use the DoubleToStr() function. Data of bool,
     * datetime and color types will be output as their numeric presentation. To
     * output values of datetime type as strings, convert them with the
     * TimeToStr() function. See also Alert() and Print() functions.
     */
    protected void Comment(Object... obj) {
        if (chart != null) {
            if (box != null) {
                chart.comment(box.multiargToString("", obj));
            } else {
                if (obj != null) {
                    chart.comment(obj[0] + "");
                }
            }
        }
    }

    static DecimalFormat dfPrint = new DecimalFormat("#,##0.##########");

    protected void Print(Object... str) {
        if (box != null) {
            box.print(str);
        } else {
            for (Object object : str) {
                System.out.println(object);
            }

        }
    }

    // The GetTickCount() function retrieves the number of milliseconds that
    // have elapsed since the system was started. It is limited to the
    // resolution of the system timer.
    int GetTickCount__() {
        return 0;
    }

    // Returns various data about securities listed in the Market Watch window.
    // A part of information about the current security is stored in predefined
    // variables.

    protected double MarketInfo(Instrument symbol, int type) {

        if (symbol == null) {
            symbol = currentInstrument;
        }
        IBar bar = null;
        switch (type) {

        case MODE_LOW:
            try {
                bar = history.getBar(symbol, Period.DAILY, OfferSide.BID, 0);
            } catch (JFException e) {

                e.printStackTrace();
            }
            if (bar != null) {
                return bar.getLow();// Low day price.
            }
            notSupported();
            return 0;

        case MODE_HIGH:
            try {
                bar = history.getBar(symbol, Period.DAILY, OfferSide.BID, 0);
            } catch (JFException e) {

                e.printStackTrace();
            }
            if (bar != null) {
                return bar.getHigh();// High day price.
            }
            notSupported();
            return 0;

        case MODE_TIME:
            return LastTickTimeMQLFormat;// The last incoming tick time
            // (last known server time).

        case MODE_BID:
            ITick iTick = null;

            try {
                iTick = history.getLastTick(symbol);
            } catch (JFException e1) {
                e1.printStackTrace();
            }
            if (iTick != null) {
                return iTick.getBid();
            }
            // Last incoming bid price. For the current symbol,
            // it is stored in the predefined variable Bid

        case MODE_ASK:
            iTick = null;

            try {
                iTick = history.getLastTick(symbol);
            } catch (JFException e1) {
                e1.printStackTrace();
            }
            if (iTick != null) {
                return iTick.getAsk();
            }

            return 0;// Last incoming ask price. For the current symbol,
            // it is stored in the predefined variable Ask

        case MODE_POINT:
            return symbol.getPipValue();// Point size in the quote currency. For
            // the
            // current symbol, it is stored in the
            // predefined variable Point

        case MODE_DIGITS:

            return symbol.getPipScale();
            // Count of digits after decimal point in the
            // symbol prices. For the current symbol, it is
            // stored in the predefined variable Digits

        case MODE_SPREAD:
            ITick tick = null;
            double tAsk = Ask;
            double tBid = Bid;
            try {
                tick = history.getLastTick(symbol);
                tAsk = tick.getAsk();
                tBid = tick.getBid();
            } catch (JFException e1) {
                e1.printStackTrace();
            }

            return NormalizeDouble((tAsk - tBid) / symbol.getPipValue(), 7);// Spread
            // value
            // in points.

        case MODE_STOPLEVEL:
            return 1;// Stop level in points.

        case MODE_LOTSIZE:
            return 1E6;// Lot size in the base currency.

        case MODE_TICKVALUE:
            double rc = 0;
            double lot = MarketInfo(symbol, MODE_LOTSIZE);
            double pip = symbol.getPipValue();
            try {
                rc = box.convertMoney(lot * pip, symbol.getSecondaryCurrency(), baseCurrency);
            } catch (JFException e) {
                e.printStackTrace();
            }
            // notSupported();
            return rc;// Tick value in the deposit currency.

        case MODE_TICKSIZE:
            notSupported();
            return 0;// Tick size in points.

        case MODE_SWAPLONG:
            notSupported();
            return 0;// Swap of the long position.

        case MODE_SWAPSHORT:
            notSupported();
            return 0;// Swap of the short position.

        case MODE_STARTING:
            notSupported();
            return 0;// Market starting date (usually used for futures).

        case MODE_EXPIRATION:
            notSupported();
            return 0;// Market expiration date (usually used for futures).

        case MODE_TRADEALLOWED:
            notSupported();
            return 0;// Trade is allowed for the symbol.

        case MODE_MINLOT:
            return 0.1;// Minimum permitted amount of a lot.

        case MODE_LOTSTEP:
            return 0.1;// Step for changing lots.

        case MODE_MAXLOT:
            return 500;// Maximum permitted amount of a lot.

        case MODE_SWAPTYPE:
            notSupported();
            return 0;// Swap calculation method. 0 - in points; 1 - in the
            // symbol base currency; 2 - by interest; 3 - in the
            // margin currency.

        case MODE_PROFITCALCMODE:
            return 0;// Profit calculation mode. 0 - Forex; 1 - CFD; 2 -
            // Futures.

        case MODE_MARGINCALCMODE:
            return 0;// Margin calculation mode. 0 - Forex; 1 - CFD; 2 -
            // Futures; 3 - CFD for indices.

        case MODE_MARGININIT:
            notSupported();
            return 0;// Initial margin requirements for 1 lot.

        case MODE_MARGINMAINTENANCE:
            notSupported();
            return 0;// Margin to maintain open positions calculated for 1
            // lot.

        case MODE_MARGINHEDGED:
            notSupported();
            return 0;// Hedged margin calculated for 1 lot.

        case MODE_MARGINREQUIRED:
            notSupported();
            return 0;// Free margin required to open 1 lot for buying.

        case MODE_FREEZELEVEL:
            notSupported();
            return 0;// Order freeze level in points. If the execution price
            // lies within the range defined by the freeze
            // level, the order cannot be modified, cancelled or
            // closed.

        default:
            notSupported();
            return 0;

        }

    }

    /*
     * The Sleep() function suspends execution of the current expert within the
     * specified interval. The Sleep() function cannot be called from custom
     * indicators since they calculate in the interface thread and may not
     * decelerate it. The checking of the expert stop flag status every 0.1
     * second has been built into the function.
     */
    protected void Sleep(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (Exception e) {

            e.printStackTrace();
        }
    }

    /**
     * Function plays a sound file. The file must be located in the files\sounds
     * directory or in its subdirectory.
     * 
     * @param filename
     * 
     */

    protected boolean PlaySound(String filename) {
        return box.playSound("sounds" + File.separator + filename);
    }

    // -----------------------------------------------------------------------------------------------
    // File functions
    // -----------------------------------------------------------------------------------------------
    // private int fileHandlerCounter = 1;
    private int generateFileHandler(boolean isBinFile) {
        BitSet bitSet = new BitSet();
        bitSet.set(7, isBinFile);
        int handler = intFromBitset(bitSet);
        while (filesMap.containsKey(handler) || handler <= 0) {
            handler++;

        }

        return handler;
    }

    private boolean isHandlerForBinFile(int handler) {
        BitSet bitSet = bitSetFromInt(handler);
        return bitSet.get(7);
    }

    private File getFilesForStrategiesDir() {
        File dir = new File(getMyStrategiesDir().getAbsolutePath() + File.separator + "files");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return dir;
    }

    private File getMyStrategiesDir() {
        File myDocs = FileSystemView.getFileSystemView().getDefaultDirectory();
        File dir = new File(myDocs.getPath() + File.separator + "My Strategies");
        return dir;
    }

    /**
     * Opens file for input and/or output. Returns a file handle for the opened
     * file or -1 (if the function fails). To get the detailed error
     * information, call GetLastError() function. Notes: Files can only be
     * opened in the terminal_directory\experts\files folder
     * (terminal_directory\tester\files if for expert testing) or in its
     * subfolders. FILE_BIN and FILE_CSV modes cannot be used simultaneously. If
     * FILE_WRITE does not combine with FILE_READ, a zero-length file will be
     * opened. If even the file contain some data, they will be deleted. If
     * there is a need to add data to an existing file, it must be opened using
     * combination of FILE_READ | FILE_WRITE. If FILE_READ does not combine with
     * FILE_WRITE, the file will be opened only if it already exists. If the
     * file does not exist, it can be created using the FILE_WRITE mode. No more
     * than 32 files can be opened within an executable module simultaneously.
     * Handles of files opened in the same module cannot be passed to other
     * modules (libraries).
     * 
     * @param filename
     *            - Filename.
     * @param mode
     *            - Opening mode. It can be one or combination of values:
     *            FILE_BIN, FILE_CSV, FILE_READ, FILE_WRITE.
     * @param delimiter
     *            - Delimiter character for csv files. By default, the ';'
     *            symbol applies.
     * @return
     */

    protected static final int FILE_READ = 1;

    protected static final int FILE_WRITE = 2;

    protected static final int FILE_BIN = 4;

    protected static final int FILE_CSV = 8;

    protected int FileOpen(String filename, int mode, char delimiter) {

        int rc = -1;
        if (filename == null || filename.indexOf(File.separator) != -1 || filename.indexOf(File.pathSeparator) != -1) {
            return rc;
        }

        try {
            File theFile = new File(getFilesForStrategiesDir() + File.separator + filename);

            String modeStr = "r";

            BitSet bitSet = bitSetFromInt(mode);
            // FILE_BIN and FILE_CSV modes cannot be used simultaneously.
            if ((bitSet.get(2) && bitSet.get(3)) || (!bitSet.get(2) && !bitSet.get(3))) {
                return -1;
            }

            // FILE_READ and FILE_WRITE are combined
            if (bitSet.get(0) && bitSet.get(1)) {
                // add to file
                if (!theFile.exists()) {
                    // FILE_WRITE means we need to create if doesn exists
                    if (bitSet.get(1)) {
                        File dir = theFile.getParentFile();
                        dir.mkdirs();
                        if (!theFile.createNewFile()) {
                            return rc;
                        }
                    } else {
                        return rc;
                    }
                }
                // mode
                if (bitSet.get(1)) {
                    modeStr = "rw";
                }
                // FILE WRITE
            } else if (bitSet.get(1)) {
                // delete existing file
                if (theFile.exists()) {
                    if (!theFile.delete()) {
                        return rc;
                    }
                }
                if (!theFile.createNewFile()) {
                    return rc;
                }
                modeStr = "rw";
            } else {

            }

            // check if we have more then 32 handlers already opened
            rc = generateFileHandler(bitSet.get(2));
            if (filesMap.size() > 32) {
                return -1;
            }

            RandomAccessFile accessFile = new RandomAccessFile(theFile, modeStr);

            filesMap.put(rc, accessFile);
            filesDelimiters.put(rc, delimiter);
        } catch (Exception e) {
            e.printStackTrace();
            return rc;
        }

        return rc;
    }

    protected int FileOpen(String filename, int mode) {
        return FileOpen(filename, mode, ';');

    }

    /**
     * Closes file previously opened by the FileOpen() function.
     */
    protected void FileClose(int handle) {
        RandomAccessFile accessFile = filesMap.get(handle);
        if (accessFile != null) {
            try {
                accessFile.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        filesMap.remove(handle);
        filesDelimiters.remove(handle);
    }

    /**
     * The function returns file size in bytes. To get the detailed error
     * information, one has to call the GetLastError() function.
     */
    protected int FileSize(int handle) {
        int rc = -1;

        RandomAccessFile accessFile = filesMap.get(handle);

        if (accessFile != null) {
            try {
                rc = (int) accessFile.length();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return rc;

    }

    /**
     * The function reads the string from the current file position. Applies to
     * both CSV and binary files. For text files, the string will be read before
     * the delimiter. For binary file, the given count of characters will be
     * read to the string. To get the detailed error information, one has to
     * call the GetLastError() function.
     */
    protected String FileReadString(int handle) {

        // BINARY NOT IMPLEMENTED YET
        if (isHandlerForBinFile(handle)) {
            return null;
        }
        StringBuilder rc = new StringBuilder();
        RandomAccessFile accessFile = filesMap.get(handle);
        Character delimiter = filesDelimiters.get(handle);

        if (accessFile != null) {
            try {
                char c = Character.MAX_VALUE;
                do {
                    if (c != Character.MAX_VALUE) {
                        rc.append(c);
                    }
                    c = (char) accessFile.read();

                } while (c != delimiter && c != '\n' && c != Character.MAX_VALUE);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return rc.toString().trim();
    }

    /**
     * The function moves the file pointer to a new position that is an offset,
     * in bytes, from the beginning, the end or the current file position. The
     * next reading or writing are made at a new position. If file pointer has
     * been moved successfully, the function returns TRUE, otherwise, it returns
     * FALSE. To get the detailed error information, one has to call the
     * GetLastError() function.
     * 
     * @param handle
     *            - File handle returned by the FileOpen() functions.
     * @param offset
     *            - Offset, in bytes, from origin.
     * @param origin
     *            - Initial position. Value can be one of the following
     *            constants: SEEK_CUR - from current position, SEEK_SET - from
     *            begin, SEEK_END - from end of file.
     * @return
     */

    protected static final int SEEK_SET = 0;

    protected static final int SEEK_CUR = 1;

    protected static final int SEEK_END = 2;

    protected boolean FileSeek(int handle, int offset, int origin) {
        boolean rc = false;
        RandomAccessFile accessFile = filesMap.get(handle);
        if (accessFile != null) {
            try {
                long originPosition = 0;
                if (origin == SEEK_SET) {
                    originPosition = 0;
                } else if (origin == SEEK_CUR) {
                    originPosition = accessFile.getFilePointer();
                } else if (origin == SEEK_END) {
                    originPosition = accessFile.length();
                } else {
                    notSupported();
                }
                accessFile.seek(originPosition + offset);
                rc = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return rc;
    }

    /**
     * The function is intended for writing of data into a CSV file, delimiter
     * being inserted automatically. After writing into the file, the line end
     * character "\r\n" will be added. Numbers will be converted into a text at
     * output (see the Print() function). Returns the count of written
     * characters or a negative number if an error occurs. To get the detailed
     * error information, one has to call the GetLastError() function.
     * 
     * @param handle
     *            - File handle returned by the FileOpen() function.
     * @param objs
     *            - User data to write, separated by commas. It can be up to 63
     *            parameters. Data of int and double types are automatically
     *            converted into a string, but those of color, datetime and bool
     *            typesare not automatically converted and will be written to
     *            file as they are (as integers). Arrays cannot be passed as a
     *            parameter, they can be output elementwise.
     * @return
     */
    protected int FileWrite(int handle, Object... objs) {
        int rc = -1;
        if (isHandlerForBinFile(handle)) {
            return -1;
        }
        RandomAccessFile accessFile = filesMap.get(handle);
        Character delimiter = filesDelimiters.get(handle);
        if (accessFile != null) {
            try {
                String out = box.multiargToString(delimiter + "", objs) + "\r\n";
                rc = out.length();
                accessFile.write(out.getBytes("UTF-8"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return rc;
    }

    // -----------------------------------------------------------------------------------------------
    // Global variables
    // -----------------------------------------------------------------------------------------------
    /**
     * A group of functions intended for working with global variables. Global
     * variables of the client terminal should not be mixed up with variables
     * declared in the global scope of the MQL4 program. Global variables are
     * kept in the client terminal within 4 weeks since the last access, then
     * they will be deleted automatically. An access to a global variable is not
     * only setting of a new value, but reading of the global variable value, as
     * well. Global variables of the client terminal are accessible
     * simultaneously from all MQL4 programs launched in the client terminal.
     */

    // Returns TRUE if the global variable exists, otherwise, returns FALSE. To
    // get the detailed error information, one has to call the GetLastError()
    // function.
    protected boolean GlobalVariableCheck(String name) {
        ISettings settings = box.getSettings("global");
        return settings.get(name) != null;

    }

    // Deletes the global variable. If the function succeeds, the returned value
    // will be TRUE, otherwise, it will be FALSE. To get the detailed error
    // information, one has to call the GetLastError() function.
    protected boolean GlobalVariableDel(String name) {
        ISettings settings = box.getSettings("global");
        settings.remove(name);
        return true;
    }

    // Returns the value of an existing global variable or 0 if an error occurs.
    // To get the detailed error information, one has to call the GetLastError()
    // function.
    protected double GlobalVariableGet(String name) {
        ISettings settings = box.getSettings("global");
        return parseDouble(settings.get(name));

    }

    protected boolean GlobalVariableGet_Boolean(String name) {
        ISettings settings = box.getSettings("global");
        double dbl = parseDouble(settings.get(name));
        boolean rc = false;
        if (dbl > 0) {
            rc = true;
        }
        return rc;

    }

    // Returns the value of an existing global variable or 0 if an error occurs.
    // To get the detailed error information, one has to call the GetLastError()
    // function.
    protected String GlobalVariableName__(int index) {
        return null;

    }

    // Sets a new value of the global variable. If it does not exist, the system
    // creates a new global variable. If the function succeeds, the returned
    // value will be the last access time. Otherwise, the returned value will be
    // 0. To get the detailed error information, one has to call the
    // GetLastError() function.
    protected long GlobalVariableSet(String name, double value) {
        ISettings settings = box.getSettings("global");
        settings.put(name, formatDouble(value));
        return 0;

    }

    protected long GlobalVariableSet(String name, boolean value) {
        ISettings settings = box.getSettings("global");
        settings.put(name, value ? "1" : "0");
        return 0;

    }

    // Sets the new value of the existing global variable if the current value
    // equals to the third parameter check_value. If there is no global
    // variable, the function will generate error ERR_GLOBAL_VARIABLE_NOT_FOUND
    // (4058) and return FALSE. When successfully executed, the function returns
    // TRUE, otherwise, it returns FALSE. To get the detailed error information,
    // one has to call the GetLastError() function.
    // If the current value of the global variable differs from the check_value,
    // the function will return FALSE.
    // The function provides atomic access to the global variable, this is why
    // it can be used for providing of a semaphore at interaction of several
    // experts working simultaneously within one client terminal.
    protected boolean GlobalVariableSetOnCondition__(String name, double value, double check_value) {
        return false;
    }

    // Deletes global variables. If the name prefix is not specified, all global
    // variables will be deleted. Otherwise, only those variables will be
    // deleted, the names of which begin with the specified prefix. The function
    // returns the count of deleted variables.
    protected int GlobalVariablesDeleteAll__(String prefix_name) {
        return 0;
    }

    // The function returns the total count of global variables.
    protected int GlobalVariablesTotal__() {
        return 0;
    }

    // -----------------------------------------------------------------------------------------------
    // Math & Trig
    // -----------------------------------------------------------------------------------------------

    /*
     * Returns the absolute value (modulus) of the specified numeric value.
     */

    protected double MathAbs(double value) {
        return Math.abs(value);
    }

    protected double MathMax(double value1, double value2) {
        return Math.max(value1, value2);
    }

    protected double MathMin(double value1, double value2) {
        return Math.min(value1, value2);
    }

    protected double MathFloor(double value) {
        return Math.floor(value);
    }

    protected double MathCeil(double value) {
        return Math.ceil(value);
    }

    /*
     * Returns value rounded to the nearest integer of the specified numeric
     * value.
     */
    protected int MathRound(double value) {
        return (int) Math.round(value);
    }

    /**
     * 
     * The MathSrand() function sets the starting point for generating a series
     * of pseudorandom integers. To reinitialize the generator, use 1 as the
     * seed argument. Any other value for seed sets the generator to a random
     * starting point. MathRand retrieves the pseudorandom numbers that are
     * generated. Calling MathRand before any call to MathSrand generates the
     * same sequence as calling MathSrand with seed passed as 1.
     */
    private Random randomMath = new Random();

    protected void MathSrand(long seed) {
        if (seed == 1) {
            randomMath.setSeed(System.currentTimeMillis());
        } else {
            randomMath.setSeed((long) seed);
        }

    }

    /**
     * The MathLog function returns the natural logarithm of x if successful. If
     * x is negative, these functions return NaN (indeterminate value). If x is
     * 0, they return INF (infinity).
     * 
     * @param x
     * @return
     */
    protected double MathLog(double x) {
        return Math.log(x);
    }

    /**
     * 
     * The MathRand function returns a pseudorandom integer within the range of
     * 0 to 32767. The MathSrand function must be used to seed the
     * pseudorandom-number generator before calling MathRand.
     * 
     */
    protected int MathRand() {
        return randomMath.nextInt(32767);
    }

    // Returns the value of the base expression raised to the specified power
    // (exponent value).
    protected double MathPow(double base, double exponent) {
        return Math.pow(base, exponent);
    }

    // The MathSqrt function returns the square root of x. If x is negative,
    // MathSqrt returns an indefinite (same as a quiet NaN).
    protected double MathSqrt(double x) {
        if (x <= 0) {
            return Double.NaN;
        }
        return Math.sqrt(x);
    }

    // -----------------------------------------------------------------------------------------------
    // Special constants
    // -----------------------------------------------------------------------------------------------
    // Special constants used to indicate parameters and variables states. It
    // can be one of the following values:

    protected static final int EMPTY = -1;// Indicates empty state of the
    // parameter.

    protected static final int EMPTY_VALUE = 0x7FFFFFFF;// Default custom
    // indicator empty
    // value.

    protected final Color CLR_NONE = Color.white;// Indicates empty state of
    // colors.

    protected static final int WHOLE_ARRAY = 0;// Used with array functions.

    // Indicates that all array
    // elements will be processed.

    /*
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * ARRAYS
     */

    /**
     * Copies an array to another one. Arrays must be of the same type, but
     * arrays with type double[], int[], datetime[], color[], and bool[] can be
     * copied as arrays of the same type. Returns the amount of copied elements.
     */
    protected int ArrayCopy(Object dest, Object source, int start_dest, int start_source, int count) {
        System.arraycopy(source, start_source, dest, start_dest, count);
        return count;
    }

    /**
     * 
     * Sorts numeric arrays by first dimension. Series arrays cannot be sorted
     * by ArraySort().
     */
    protected int ArraySort(double array[]) {
        return ArraySort(array, array.length);
    }

    protected int ArraySort(double array[], int count) {
        return ArraySort(array, count, 0);
    }

    protected int ArraySort(double array[], int count, int start) {
        return ArraySort(array, count, start, MODE_ASCEND);
    }

    protected int ArraySort(double array[], int count, int start, int sort_dir) {
        if (sort_dir == MODE_DESCEND) {
            Double[] doubleArray = toObject(array);
            Arrays.sort(doubleArray, start, start + count, Collections.reverseOrder());
            System.arraycopy(toPrimitive(doubleArray), 0, array, 0, doubleArray.length);
        } else {
            Arrays.sort(array, start, start + count);
        }
        return 0;
    }

    // Array functions
    /**
     * If the element with the specified value doesn't exist in the array, the
     * function returns the index of the nearest smallest value of the elements
     * between which the searched value is located. The function cannot be used
     * with string arrays and series arrays (with the exception of the series
     * array of the bar open time). Note: Binary search processes only sorted
     * arrays. To sort numeric arrays use the ArraySort() function.
     * 
     * @param array
     *            - The numeric array to search for.
     * @param value
     *            - The value to search for.
     * @param count
     *            - Count of elements to search for. By default, it searches in
     *            the whole array.
     * @param start
     *            - Starting index to search for. By default, the search starts
     *            at the first element.
     * @param direction
     *            - Search direction. It can be any of the following values:
     *            MODE_ASCEND searching in forward direction, MODE_DESCEND
     *            searching in backward direction.
     * 
     * @return index of the nearest smallest value of the elements between which
     *         the searched value is located. int ArrayBsearch(double array[],
     *         double value, int count=WHOLE_ARRAY, int start=0, int
     *         direction=MODE_ASCEND)
     */
    protected int ArrayBsearch(double array[], double value) {
        return Arrays.binarySearch(array, value);
    }

    protected int ArrayBsearch(double array[], double value, int count) {
        return ArrayBsearch(array, value, count, 0, MODE_ASCEND);
    }

    protected int ArrayBsearch(double array[], double value, int count, int start) {
        return ArrayBsearch(array, value, count, start, MODE_ASCEND);
    }

    protected int ArrayBsearch(double array[], double value, int count, int start, int direction) {
        int end = start + count;
        if (count == WHOLE_ARRAY) {
            end = array.length - 1;
        }
        if (direction == MODE_ASCEND) {
            return Arrays.binarySearch(array, start, end, value);
        } else {
            Double[] doubleArray = toObject(array);
            return Arrays.binarySearch(doubleArray, start, end, value, Collections.reverseOrder());
        }
    }

    /**
     * 
     * @param array
     *            - Array for which the rank will be returned.
     * @return the multidimensional array rank. int ArrayDimension( object
     *         array[])
     */
    protected int ArrayDimension(Object array) {

        return arrayDimensionInfo(array)[0];
    }

    /**
     * 
     * @param array
     *            - Array to be checked.
     * @return boolean ArrayGetAsSeries( object array[])
     */
    protected boolean ArrayGetAsSeries(Object array) {
        throw new RuntimeException("This function not allowed");
    };

    /**
     * Sets all elements of a numeric array to the same value. Note: It is not
     * recommended to initialize index buffers in the custom indicator init()
     * function as such functions are initialized automatically with an
     * "empty value" at allocation and re-allocation of buffers.
     * 
     * @param array
     *            - Numeric array to be initialized.
     * @param value
     *            - New value to be set.
     * @return the count of initialized elements. int ArrayInitialize( void
     *         array[], double value) Now function work with one dimention
     *         arrays
     */
    // private Object getRoww(Object obj, int index, int dim_number, int
    // dim_count, int[] count) {
    // if(dim_number == dim_count-2) {
    // return Array.get(obj, index);
    // }
    // Object obj0 = Array.get(obj, )
    // getRow(Object obj, int index, int dim_number, int dim_count)
    // }

    protected int ArrayInitialize(Object obj, double value) {
        int dim[] = arrayDimensionInfo(obj);
        for (int i = 0; i < dim[0]; i++) {
            Array.set(obj, i, value);
        }
        return dim[0];
        // throw new RuntimeException("This function not allowed");
    }

    /**
     * 
     * @param array
     *            - Array under check.
     * @return TRUE if the array under check is a series array
     *         (Time[],Open[],Close[],High[],Low[], or Volume[]), otherwise
     *         returns FALSE. bool ArrayIsSeries( object array[])
     */
    protected boolean ArrayIsSeries(Object array) {
        throw new RuntimeException("This function not allowed");
    };

    /**
     * Searches for the element with maximum value.
     * 
     * @param array
     *            - The numeric array to search in.
     * @param count
     *            - The amount of elements to search in.
     * @param start
     *            - Initial search index.
     * @return position of this maximum element in the array. int ArrayMaximum(
     *         double array[], int count=WHOLE_ARRAY, int start=0)
     */
    protected int ArrayMaximum(double array[]) {
        return ArrayMaximum(array, WHOLE_ARRAY, 0);
    }

    protected int ArrayMaximum(double array[], int count) {
        return ArrayMaximum(array, count, 0);
    }

    protected int ArrayMaximum(double array[], int count, int start) {
        int pos = -1;
        int end = start + count;
        int length = count;
        if (count == WHOLE_ARRAY) {
            end = array.length - 1;
            length = array.length;
        }
        double[] arrayCopy = new double[length];
        arrayCopy = Arrays.copyOfRange(array, start, end);
        Arrays.sort(arrayCopy);
        double max = arrayCopy[arrayCopy.length - 1];
        pos = Arrays.binarySearch(array, start, start + count, max);
        return pos;
    }

    /**
     * Searches for the element with minimum value.
     * 
     * @param array
     *            - The numeric array to search in.
     * @param count
     *            - The amount of elements to search in.
     * @param start
     *            - Initial search index.
     * @return position of this minimum element in the array. int
     *         ArrayMinimum(double array[], int count=WHOLE_ARRAY, int start=0)
     */
    protected int ArrayMinimum(double array[]) {
        return ArrayMinimum(array, WHOLE_ARRAY, 0);
    }

    protected int ArrayMinimum(double array[], int count) {
        return ArrayMinimum(array, count, 0);
    }

    protected int ArrayMinimum(double array[], int count, int start) {
        int pos = -1;
        int end = start + count;
        int length = count;
        if (count == WHOLE_ARRAY) {
            end = array.length - 1;
            length = array.length;
        }
        double[] arrayCopy = new double[length];
        arrayCopy = Arrays.copyOfRange(array, start, end);
        Arrays.sort(arrayCopy);
        double min = arrayCopy[0];
        pos = Arrays.binarySearch(array, start, start + count, min);
        return pos;
    }

    /**
     * 
     * @param array
     *            - Array to check
     * @param range_index
     *            - Dimension index.
     * @return the count of elements in the given dimension of the array. Since
     *         indexes are zero-based, the size of dimension is 1 greater than
     *         the largest index. int ArrayRange( object array[], int
     *         range_index)
     */
    protected int ArrayRange(Object array, int range_index) {
        int rc = 0;
        int dimention = 0;
        if (array != null) {
            Class c = array.getClass();
            while (c.isArray() && dimention <= range_index) {
                rc = Array.getLength(c);
                c = c.getComponentType();
            }
        }
        return rc;
    };

    /**
     * Sets a new size for the first dimension. If executed successfully, it
     * returns count of all elements contained in the array after resizing,
     * otherwise, returns -1, and array is not resized. Note: Array declared at
     * a local level in a function and resized will remain unchanged after the
     * function has completed its operation. After the function has been
     * recalled, such array will have a size differing from the declared one.
     * 
     * @param array
     *            - Array to resize.
     * @param new_size
     *            - New size for the first dimension.
     * @return count of all elements contained in the array after resizing,
     *         otherwise, returns -1, and array is not resized. int ArrayResize(
     *         void array[], int new_size)
     */
    protected int ArrayResize(Object array, int new_size) {
        int result = -1;
        array = resizeArray(array, new_size);
        result = new_size;
        return result;
    }

    /**
     * Sets indexing direction of the array. If the set parameter has the TRUE
     * value, the array will be indexed in a reversed order, i.e., the last
     * element has a zero index. The FALSE value sets a standard indexing order.
     * The function returns the previous status.
     * 
     * @param array
     *            - The numeric array to set.
     * @param set
     *            - Array indexing order.
     * @return boolean ArraySetAsSeries( void array[], boolean set)
     */
    protected boolean ArraySetAsSeries(Object array, boolean set) {
        throw new RuntimeException("This function not allowed");
    };

    // ------------------------------
    /**
     * Returns the count of elements contained in the array. For a
     * one-dimensional array, the value to be returned by the ArraySize function
     * is equal to that of ArrayRange(array,0).
     * 
     */
    protected int ArraySize(Object array) {
        int rc = 0;
        if (array != null) {
            Class c = array.getClass();
            while (c.isArray()) {
                rc += Array.getLength(c);
                c = c.getComponentType();
            }
        }
        return rc;
    }

    // -----------------------------------------------------------------------------------------------
    // Object functions
    // -----------------------------------------------------------------------------------------------

    protected Color RGB(int r, int g, int b) {
        return new Color(r, g, b);
    }

    /**
     * Deletes object having the specified name. If the function succeeds, the
     * returned value will be TRUE. Otherwise, it will be FALSE. To get the
     * detailed error information, one has to call the GetLastError() function.
     */
    protected boolean ObjectDelete(String name) {
        IChartObject obj = null;
        if (chart != null) {
            obj = chart.remove(name);
        }
        return obj != null;
    }

    /**
     * 
     * Returns total amount of objects of the specified type in the chart.
     */
    protected int ObjectsTotal(int type) {
        int rc = 0;
        if (chart != null) {
            if (type == EMPTY) {
                rc = chart.getAll().size();
            } else {
                for (IChartObject chartObject : chart.getAll()) {
                    if (chartObject.getType() == convertChartType(type)) {
                        rc++;
                    }
                }
            }
        }
        return rc;
    }

    protected int ObjectsTotal() {

        return ObjectsTotal(EMPTY);
    }

    /**
     * 
     * The function returns the object name by its index in the objects list. To
     * get the detailed error information, one has to call the GetLastError()
     * function.
     * 
     */
    protected String ObjectName(int index) {
        String rc = null;
        if (chart != null) {
            if (chart.getAll().size() > index && index >= 0) {
                IChartObject chartObject = chart.getAll().get(index);
                rc = chartObject.getKey();
            }

        }
        return rc;
    }

    /**
     * 
     * Search for an object having the specified name. The function returns
     * index of the windows that contains the object to be found. If it fails,
     * the returned value will be -1. To get the detailed error information, one
     * has to call the GetLastError() function. The chart sub-windows (if there
     * are sub-windows with indicators in the chart) are numbered starting from
     * 1. The chart main window always exists and has the 0 index.
     * 
     */
    protected int ObjectFind(String name) {
        int rc = 0;
        if (chart != null) {
            int counter = 1;
            for (IChartObject chartObject : chart.getAll()) {
                if (chartObject.getKey().equals(name)) {
                    rc = counter;
                    break;
                }
                counter++;
            }
        }
        return rc;

    }

    /**
     * Creation of an object with the specified name, type and initial
     * coordinates in the specified window. Count of coordinates related to the
     * object can be from 1 to 3 depending on the object type. If the function
     * succeeds, the returned value will be TRUE. Otherwise, it will be FALSE.
     * To get the detailed error information, one has to call the GetLastError()
     * function. Objects of the OBJ_LABEL type ignore the coordinates. Use the
     * function of ObjectSet() to set up the OBJPROP_XDISTANCE and
     * OBJPROP_YDISTANCE properties. Notes: The chart sub-windows (if there are
     * sub-windows with indicators in the chart) are numbered starting from 1.
     * The chart main window always exists and has the 0 index. Coordinates must
     * be passed in pairs: time and price. For example, the OBJ_VLINE object
     * needs only time, but price (any value) must be passed, as well.
     * 
     * 
     * @param name
     *            - Object unique name.
     * @param type
     *            - Object type. It can be any of the Object type enumeration
     *            values.
     * @param window
     *            - Index of the window where the object will be added. Window
     *            index must exceed or equal to 0 and be less than
     *            WindowsTotal().
     * @param time1
     *            - Time part of the first point.
     * @param price1
     *            - Price part of the first point.
     * @param time2
     *            - Time part of the second point.
     * @param price2
     *            - Price part of the second point.
     * @param time3
     *            - Price part of the third point.
     * @param price2
     *            - Price part of the third point.
     * 
     * @return
     */
    protected boolean ObjectCreate(String name, int type, int window, long time1, double price1, long time2, double price2, long time3, double price3) {
        IChart.Type chartType = convertChartType(type);
        IChartObject chartObject = null;
        if (chart != null) {
            chartObject = chart.draw(name, chartType, time1 * 1000, price1, time2 * 1000, price2, time3 * 1000, price3);
        }
        return chartObject != null;

    }

    protected boolean ObjectCreate(String name, int type, int window, long time1, double price1, long time2, double price2) {
        return ObjectCreate(name, type, window, time1, price1, time2, price2, 0, 0);
    }

    protected boolean ObjectCreate(String name, int type, int window, long time1, double price1) {
        return ObjectCreate(name, type, window, time1, price1, 0, 0);
    }

    /**
     * Changes the object description. For objects of OBJ_TEXT and OBJ_LABEL,
     * this description is shown as a text line in the chart. If the function
     * succeeds, the returned value will be TRUE. Otherwise, it is FALSE. To get
     * the detailed error information, one has to call the GetLastError()
     * function. Parameters of font_size, font_name and text_color are used for
     * objects of OBJ_TEXT and OBJ_LABEL only. For objects of other types, these
     * parameters are ignored. See also ObjectDescription() function.
     * 
     * @param name
     *            - Object name.
     * @param text
     *            - A text describing the object.
     * @param font_size
     *            - Font size in points.
     * @param font
     *            - Font name.
     * @param text_color
     *            - Text color.
     * @return
     */
    protected boolean ObjectSetText(String name, String text, int font_size, String font, Color text_color) {
        IChartObject chartObject = null;
        if (chart != null) {
            chartObject = chart.get(name);
        }
        if (chartObject != null) {
            chartObject.setText(text);
            return true;
        }
        return false;

    }

    protected void ObjectsRedraw() {

    }

    /**
     * Changes the value of the specified object property. If the function
     * succeeds, the returned value will be TRUE. Otherwise, it will be FALSE.
     * To get the detailed error information, one has to call the GetLastError()
     * function. See also ObjectGet() function. Parameters: name - Object name.
     * index - Object value index. It can be any of Object properties
     * enumeration values. value - New value of the given property. Sample:
     * 
     * // moving the first coord to the last bar time ObjectSet("MyTrend",
     * OBJPROP_TIME1, Time[0]); // setting the second fibo level
     * ObjectSet("MyFibo", OBJPROP_FIRSTLEVEL+1, 1.234); // setting object
     * visibility. object will be shown only on 15 minute and 1 hour charts
     * ObjectSet("MyObject", OBJPROP_TIMEFRAMES, OBJ_PERIOD_M15 |
     * OBJ_PERIOD_H1);
     * 
     * @throws JFException
     */

    protected boolean ObjectSet(String name, int index, boolean value) {
        return ObjectSet(name, index, value ? 1 : 0);
    }

    protected boolean ObjectSet(String name, int index, double value) {
        IChartObject chartObject = null;
        if (chart != null) {
            chartObject = chart.get(name);
        }
        if (chartObject != null) {
            ATTR_BOOLEAN attr_boolean = getObjectPropBoolean(index);

            ATTR_COLOR attr_color = getObjectPropColor(index);
            ATTR_DOUBLE attr_double = getObjectPropDouble(index);
            ATTR_INT attr_int = getObjectPropInt(index);
            ATTR_LONG attr_long = getObjectPropLong(index);

            if (attr_boolean != null) {
                chartObject.setAttrBoolean(attr_boolean, value > 0 ? true : false);
            } else if (attr_color != null) {
                chartObject.setAttrColor(attr_color, new Color((int) value));
            } else if (attr_double != null) {
                chartObject.setAttrDouble(attr_double, value);
            } else if (attr_int != null) {
                chartObject.setAttrInt(attr_int, (int) value);
            } else if (attr_long != null) {
                chartObject.setAttrLong(attr_long, (int) value);
            } else {
                throw new RuntimeException("unknown object attribute. " + index);
            }

        }
        return true;
    }

    /**
     * Returns maximal value of the vertical scale of the specified subwindow of
     * the current chart (0-main chart window, the indicators' subwindows are
     * numbered starting from 1). If the subwindow index has not been specified,
     * the maximal value of the price scale of the main chart window is
     * returned. See also WindowPriceMin(), WindowFirstVisibleBar(),
     * WindowBarsPerChart()
     * 
     * @param index
     * @return
     * @throws JFException
     */

    protected double WindowPriceMax() {
        return WindowPriceMax(0);
    }

    protected double WindowPriceMax(int index) {
        if (index < 0) {
            notSupported();
        }
        if (chart != null) {
            return chart.priceMax(0);
        }
        return 0;

    }

    /**
     * Returns minimal value of the vertical scale of the specified subwindow of
     * the current chart (0-main chart window, the indicators' subwindows are
     * numbered starting from 1). If the subwindow index has not been specified,
     * the minimal value of the price scale of the main chart window is
     * returned. See also WindowPriceMax(), WindowFirstVisibleBar(),
     * WindowBarsPerChart()
     * 
     * @param index
     * @return
     * @throws JFException
     */
    protected double WindowPriceMin() {
        return WindowPriceMin(0);
    }

    double WindowPriceMin(int index) {
        if (index < 0) {
            notSupported();
        }
        if (chart != null) {
            return chart.priceMin(index);
        }
        return 0;
    }

    // -----------------------------------------------------------------------------------------------
    // Account information
    // -----------------------------------------------------------------------------------------------

    // Returns free margin value of the current account.
    protected double AccountFreeMargin() {
        if (creditLine == 0) {
            notSupported();
        }
        return creditLine / leverage;
    }

    // Returns free margin that remains after the specified position has been
    // opened at the current price on the current account.
    // If the free margin is insufficient, an error 134 (ERR_NOT_ENOUGH_MONEY)
    // will be generated.
    protected double AccountFreeMarginCheck(Instrument symbol, int cmd, double volume) {
        ITick lastTick = null;
        try {
            lastTick = history.getLastTick(symbol);
        } catch (JFException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        double rc = 0;
        if (lastTick != null) {
            // IEngine.OrderCommand command = getOrderCommand(cmd);

            rc = AccountFreeMargin() - lastTick.getBid() * volume * 1E6;
            if (rc < 0) {
                rc = 0;
                lastError = ERR_NOT_ENOUGH_MONEY;
            }

        }
        return rc;

    }

    // Returns margin value of the current account.
    protected double AccountMargin() {
        if (creditLine == 0) {
            notSupported();
        }
        return creditLine / leverage;

    }

    // Returns leverage of the current account.
    protected int AccountLeverage() {
        return (int) leverage;
    }

    // Returns the brokerage company name where the current account was
    // registered.
    protected String AccountCompany() {
        return "Dukascopy (Suisse) SA";
    }

    // Returns credit value of the current account.
    protected double AccountCredit() {
        return 0;
    }

    protected String AccountCurrency() {
        return baseCurrency.toString();
    }

    protected double AccountEquity() {
        return equity;
    }

    protected String AccountName() {
        return engine.getAccount();
    }

    protected int AccountNumber() {
        return 0;
    }

    protected double AccountProfit() {
        return 0;
    }

    /**
     * Returns balance value of the current account (the amount of money on the
     * account).
     * 
     * @return
     * @throws JFException
     */
    protected double AccountBalance() {
        if (equity == 0) {
            // throw new RuntimeException("Equity not available");
            Print("AccountBalance can not be used in init() function. Equity information available only on first onAccount message");
            context.stop();
        }

        double pnlInBaseCurrency = 0;
        try {
            for (IOrder order : engine.getOrders()) {
                if (order.getState() == IOrder.State.FILLED) {
                    double profit = box.calculateProfitMoney(order);
                    pnlInBaseCurrency += box.convertMoney(profit, order.getInstrument().getSecondaryCurrency(), baseCurrency);
                }
            }
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
            return 0;
        }

        return equity - pnlInBaseCurrency;
    }

    // -----------------------------------------------------------------------------------------------
    // Date & Time functions
    // -----------------------------------------------------------------------------------------------

    /*
     * Returns year for the specified date. The returned value can be within the
     * range of 1970 to
     */
    protected int TimeYear(long time) {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(time * 1000);
        return calendar.get(Calendar.YEAR);
    }

    /*
     * Returns the month number for the specified time.
     */

    protected int TimeMonth(long time) {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(time * 1000);
        return calendar.get(Calendar.MONTH) + 1;
    }

    // Returns the hour for the specified time.
    protected int TimeHour(long time) {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(time * 1000);
        return calendar.get(Calendar.HOUR_OF_DAY);
    }

    // int TimeDay( datetime date)
    // Returns day of month (1 - 31) for the specified date.
    protected int TimeDay(long time) {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(time * 1000);
        return calendar.get(Calendar.DAY_OF_MONTH);
    }

    /*
     * Returns the current year, i.e., the year of the last known server time.
     * Note: At the testing, the last known server time is modelled.
     */

    protected int Year() {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(lastTickTime);
        return calendar.get(Calendar.YEAR);

    }

    /*
     * Returns the current month as number (1-January,2,3,4,5,6,7,8,9,10,11,12),
     * i.e., the number of month of the last known server time. Note: At the
     * testing, the last known server time is modelled.
     */
    protected int Month() {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(lastTickTime);
        return calendar.get(Calendar.MONTH) + 1;
    }

    /*
     * Returns the current day of the month, i.e., the day of month of the last
     * known server time. Note: At the testing, the last known server time is
     * modelled.
     */
    protected int Day() {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(lastTickTime);
        return calendar.get(Calendar.DATE);
    }

    // Returns the hour (0,1,2,..23) of the last known server time by the moment
    // of the program start (this value will not change within the time of the
    // program execution).
    // Note: At the testing, the last known server time is modelled.
    protected int Hour() {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(lastTickTime);
        return calendar.get(Calendar.HOUR_OF_DAY);

    }

    // Returns the current minute (0,1,2,..59) of the last known server time by
    // the moment of the program start
    // (this value will not change within the time of the program execution).

    protected int Minute() {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(lastTickTime);
        return calendar.get(Calendar.MINUTE);

    }

    // Returns the current zero-based day of the week (0-Sunday,1,2,3,4,5,6) of
    // the last known server time.
    // Note: At the testing, the last known server time is modelled.
    protected int DayOfWeek() {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(lastTickTime);
        return calendar.get(Calendar.DAY_OF_WEEK) - 1;

    }

    // Returns the zero-based day of week (0 means Sunday,1,2,3,4,5,6) for the
    // specified date.
    protected int TimeDayOfWeek(long date) {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(date * 1000);
        return calendar.get(Calendar.DAY_OF_WEEK) - 1;
    }

    /*
     * Returns the last known server time (time of incoming of the latest quote)
     * as number of seconds elapsed from 00:00 January 1, 1970. Note: At the
     * testing, the last known server time is modelled.
     */
    protected long TimeCurrent() {
        return LastTickTimeMQLFormat;
    }

    protected long CurTime() {
        return LastTickTimeMQLFormat;
    }

    // -----------------------------------------------------------------------------------------------
    // Trading functions
    // -----------------------------------------------------------------------------------------------

    /**
     * Returns close price for the currently selected order. Note: The order
     * must be previously selected by the OrderSelect() function.
     */
    protected double OrderClosePrice() {
        double rc = 0;
        if (selectedOrder != null) {

            rc = selectedOrder.getClosePrice();
            if (rc == 0) {
                try {
                    ITick iTick = history.getLastTick(selectedOrder.getInstrument());
                    if (selectedOrder.isLong()) {
                        rc = iTick.getBid();
                    } else {
                        rc = iTick.getAsk();
                    }
                } catch (JFException e) {
                    e.printStackTrace();
                }
            }
        }
        return rc;
    }

    /**
     * Returns close time for the currently selected order. If order close time
     * is not 0 then the order selected and has been closed and retrieved from
     * the account history. Open and pending orders close time is equal to 0.
     * Note: The order must be previously selected by the OrderSelect()
     * function.
     */
    protected long OrderCloseTime() {
        long rc = 0;
        if (selectedOrder != null) {
            rc = selectedOrder.getCloseTime() / 1000;
        }
        return rc;
    }

    // Deletes previously opened pending order. If the function succeeds, the
    // return value is true.
    // If the function fails, the return value is false. To get the detailed
    // error information, call GetLastError().
    protected boolean OrderDelete(int ticket) {
        return OrderDelete(ticket, White);
    }

    protected boolean OrderDelete(int ticket, Color color) {
        boolean rc = false;
        IOrder order = engine.getOrderById(ticket + "");
        if (order != null) {
            try {
                order.close();
                rc = true;
            } catch (JFException e) {
                e.printStackTrace();
            }
        }
        return rc;
    }

    // Returns the number of closed orders in the account history loaded into
    // the terminal. The history list size depends on the current settings of
    // the "Account history" tab of the terminal.
    protected int OrdersHistoryTotal() {
        return closeOrders.size();

    }

    // Closes opened order. If the function succeeds, the return value is true.
    // If the function fails, the return value is false.
    // To get the detailed error information, call GetLastError().
    protected boolean OrderClose(int ticket, double lots, double price, int slippage) {
        return OrderClose(ticket, lots, price, slippage, White);
    }

    protected boolean OrderClose(int ticket, double lots, double price, int slippage, Color color) {
        boolean rc = false;
        IOrder order = engine.getOrderById(ticket + "");
        if (order != null) {
            if (order.getState() == IOrder.State.OPENED) {
                // workaround for opened
                if (lots == order.getRequestedAmount()) {
                    lots = 0;
                }
            }
            try {
                if (order.getState() == IOrder.State.OPENED) {
                    order.close();
                } else {
                    order.close(lots, price, slippage);
                }
                order.waitForUpdate(2000);
                rc = true;
            } catch (JFException e) {

                e.printStackTrace();
            }
        }
        return rc;
    }

    // Returns ticket number for the currently selected order.
    // Note: The order must be previously selected by the OrderSelect()
    // function.
    protected int OrderTicket() {
        int rc = 0;
        if (selectedOrder != null) {
            rc = Integer.parseInt(selectedOrder.getId());
        }
        return rc;

    }

    // Returns stop loss value for the currently selected order.
    // Note: The order must be previously selected by the OrderSelect()
    // function.
    protected double OrderStopLoss() {
        double rc = 0;
        if (selectedOrder != null) {
            rc = selectedOrder.getStopLossPrice();
        }
        return rc;
    }

    // Returns take profit value for the currently selected order.
    // Note: The order must be previously selected by the OrderSelect()
    // function.
    protected double OrderTakeProfit() {
        double rc = 0;
        if (selectedOrder != null) {
            rc = selectedOrder.getTakeProfitPrice();
        }
        return rc;
    }

    // Returns amount of lots for the selected order.
    // Note: The order must be previously selected by the OrderSelect()
    // function.
    protected double OrderLots() {
        double rc = 0;
        if (selectedOrder != null) {
            rc = selectedOrder.getAmount();
        }
        return rc;
    }

    // Returns open price for the currently selected order.
    // Order must be first selected by the OrderSelect() function.
    protected double OrderOpenPrice() {
        double rc = 0;
        if (selectedOrder != null) {
            rc = selectedOrder.getOpenPrice();
        }
        return rc;

    }

    // Returns comment for the selected order.
    // Note: The order must be previously selected by the OrderSelect()
    // function.
    protected String OrderComment() {
        String rc = null;
        if (selectedOrder != null) {
            rc = selectedOrder.getComment();
        }
        return rc;

    }

    // Returns the net profit value (without swaps or commissions) for the
    // selected order.
    // For open positions, it is the current unrealized profit. For closed
    // orders, it is the fixed profit.
    // Returns profit for the currently selected order.
    // Note: The order must be previously selected by the OrderSelect()
    // function.
    protected double OrderProfit() {
        double rc = 0;
        if (selectedOrder != null) {
            try {
                rc = box.calculateProfitMoney(selectedOrder);
                rc = box.convertMoney(rc, selectedOrder.getInstrument().getSecondaryCurrency(), baseCurrency);
            } catch (JFException e) {
                e.printStackTrace();
            }
        }
        return rc;

    }

    // Returns open time for the currently selected order.
    // Note: The order must be previously selected by the OrderSelect()
    // function.
    protected long OrderOpenTime() {
        long rc = 0;
        if (selectedOrder != null) {
            rc = selectedOrder.getFillTime() / 1000;
        }
        return rc;
    }

    // Returns the order symbol value for selected order.
    // Note: The order must be previously selected by the OrderSelect()
    // function.

    protected Instrument OrderSymbol() {
        Instrument rc = null;
        if (selectedOrder != null) {
            rc = selectedOrder.getInstrument();
        }
        return rc;
    }

    // Returns market and pending orders count.
    protected int OrdersTotal() {
        int rc = 0;
        try {
            List<IOrder> orders = engine.getOrders();

            rc = orders.size();
            log(rc + "=OrdersTotal()");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return rc;

    }

    // The function selects an order for further processing.
    // It returns TRUE if the function succeeds. It returns FALSE if the
    // function fails.
    // To get the error information, one has to call the GetLastError()
    // function.
    // The pool parameter is ignored if the order is selected by the ticket
    // number.
    // The ticket number is a unique order identifier.
    // To find out from what list the order has been selected, its close time
    // must be analyzed.
    // If the order close time equals to 0, the order is open or pending and
    // taken from the terminal open positions list.
    // One can distinguish an open position from a pending order by the order
    // type.
    // If the order close time does not equal to 0, the order is a closed order
    // or a deleted pending order and was selected from the terminal history.
    // They also differ from each other by their order types.

    protected int SELECT_BY_POS = 0;// - index in the order pool,

    protected int SELECT_BY_TICKET = 1;// - index is order ticket.

    // pool - Optional order pool index. Used when the selected parameter is
    // SELECT_BY_POS. It can be any of the following values:
    protected int MODE_TRADES = 0;// (default)- order selected from trading
    // pool(opened and pending orders),

    protected int MODE_HISTORY = 1;// - order selected from history pool (closed

    // and canceled order).

    protected boolean OrderSelect(int index, int select) {
        return OrderSelect(index, select, MODE_TRADES);
    }

    private IOrder selectedOrder = null;

    protected boolean OrderSelect(int index, int select, int pool) {
        log("OrderSelect(" + index + ", " + select + ", " + pool + ")");

        // selecting pool
        List<IOrder> list = null;
        if (pool == MODE_HISTORY) {
            list = closeOrders;
        } else if (pool == MODE_TRADES) {
            try {
                list = engine.getOrders();
            } catch (JFException e) {
                e.printStackTrace();
            }
        } else {
            notSupported();
        }

        if (select == SELECT_BY_POS) {

            if (mqldbg) {
                for (IOrder order : list) {
                    log("order-" + order);
                }
            }
            if (index >= list.size() || index < 0) {
                log("!!! - index out of bounds index=" + index + " list.size()=" + list.size());
                selectedOrder = null;
            } else {
                selectedOrder = list.get(index);
            }
            log("selected-" + selectedOrder);
        } else if (select == SELECT_BY_TICKET) {
            // selectedOrder = engine.getOrderById(index + "");

            for (IOrder order : list) {
                if (order.getId().equals(index)) {
                    selectedOrder = order;
                    log("selected-" + selectedOrder);
                    break;
                }
            }

        } else {
            notSupported();
        }
        if (selectedOrder != null) {
            return true;
        }
        return false;
    }

    // Returns order operation type for the currently selected order. It can be
    // any of the following values:
    // OP_BUY - buying position,
    // OP_SELL - selling position,
    // OP_BUYLIMIT - buy limit pending position,
    // OP_BUYSTOP - buy stop pending position,
    // OP_SELLLIMIT - sell limit pending position,
    // OP_SELLSTOP - sell stop pending position.
    protected int OrderType() {
        int rc = -1;
        if (selectedOrder != null) {
            if (selectedOrder.getOrderCommand() == IEngine.OrderCommand.BUY) {
                return OP_BUY;
            }
            if (selectedOrder.getOrderCommand() == IEngine.OrderCommand.SELL) {
                return OP_SELL;
            }
            if (selectedOrder.getOrderCommand() == IEngine.OrderCommand.BUYLIMIT) {
                return OP_BUYLIMIT;
            }
            if (selectedOrder.getOrderCommand() == IEngine.OrderCommand.BUYSTOP) {
                return OP_BUYSTOP;
            }
            if (selectedOrder.getOrderCommand() == IEngine.OrderCommand.SELLLIMIT) {
                return OP_SELLLIMIT;
            }
            if (selectedOrder.getOrderCommand() == IEngine.OrderCommand.SELLSTOP) {
                return OP_SELLSTOP;
            }
            if (selectedOrder.getOrderCommand() == IEngine.OrderCommand.PLACE_BID) {
                return OP_BUYLIMIT_BIDOFFER;
            }
            if (selectedOrder.getOrderCommand() == IEngine.OrderCommand.PLACE_OFFER) {
                return OP_SELLLIMIT_BIDOFFER;
            }

        }

        return rc;

    }

    // Returns an identifying (magic) number for the currently selected order.
    // Note: The order must be previously selected by the OrderSelect()
    // function.
    protected int OrderMagicNumber() {
        return box.getMagicNumber(selectedOrder);
    }

    /*
     * The main function used to open a position or place a pending order.
     * Returns number of the ticket assigned to the order by the trade server or
     * -1 if it fails. To get additional error information, one has to call the
     * GetLastError() function. Notes: At opening of a market order (OP_SELL or
     * OP_BUY), only the latest prices of Bid (for selling) or Ask (for buying)
     * can be used as open price. If operation is performed with a security
     * differing from the current one, the MarketInfo() function must be used
     * with MODE_BID or MODE_ASK parameter for the latest quotes for this
     * security to be obtained. Calculated or unnormalized price cannot be
     * applied. If there has not been the requested open price in the price
     * thread or it has not been normalized according to the amount of digits
     * after decimal point, the error 129 (ERR_INVALID_PRICE) will be generated.
     * If the requested open price is fully out of date, the error 138
     * (ERR_REQUOTE) will be generated independently on the slippage parameter.
     * If the requested price is out of date, but present in the thread, the
     * position will be opened at the current price and only if the current
     * price lies within the range of price+-slippage.
     * 
     * StopLoss and TakeProfit levels cannot be too close to the market. The
     * minimal distance of stop levels in points can be obtained using the
     * MarketInfo() function with MODE_STOPLEVEL parameter. In the case of
     * erroneous or unnormalized stop levels, the error 130 (ERR_INVALID_STOPS)
     * will be generated.
     * 
     * At placing of a pending order, the open price cannot be too close to the
     * market. The minimal distance of the pending price from the current market
     * one in points can be obtained using the MarketInfo() function with the
     * MODE_STOPLEVEL parameter. In case of false open price of a pending order,
     * the error 130 (ERR_INVALID_STOPS) will be generated.
     * 
     * Applying of pending order expiration time can be disabled in some trade
     * servers. In this case, when a non-zero value is specified in the
     * expiration parameter, the error 147 (ERR_TRADE_EXPIRATION_DENIED) will be
     * generated.
     * 
     * On some trade servers, the total amount of open and pending orders can be
     * limited. If this limit has been exceeded, no new position will be opened
     * (or no pending order will be placed) and trade server will return error
     * 148 (ERR_TRADE_TOO_MANY_ORDERS).
     */
    protected int OrderSend(Instrument symbol, int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, String comment, int magic) {
        return OrderSend(symbol, cmd, volume, price, slippage, stoploss, takeprofit, comment, magic, 0L);
    }

    protected int OrderSend(Instrument symbol, int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, String comment, int magic, long expiration) {
        return OrderSend(symbol, cmd, volume, price, slippage, stoploss, takeprofit, comment, magic, expiration, White);
    }

    protected int OrderSend(Instrument symbol, int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, String comment, int magic, long expiration, Color arrow_color) {
        price = box.roundHalfPip(price);
        stoploss = box.roundHalfPip(stoploss);
        takeprofit = box.roundHalfPip(takeprofit);
        log("OrderSend(symbol=" + symbol + ", cmd=" + cmd + ", volume=" + volume + ", price=" + price + ", slippage=" + slippage + ", stoploss=" + stoploss + ", takeprofit=" + takeprofit + ", comment=" + comment + ", magic=" + magic + ", expiration=" + expiration + ", arrow_color=" + arrow_color + ")");
        int rc = -1;
        String label = box.generateLabel(magic);
        IEngine.OrderCommand oCommand = getOrderCommand(cmd);

        if (expiration > 0 && cmd != OP_SELLLIMIT_BIDOFFER && cmd != OP_BUYLIMIT_BIDOFFER) {
            notSupported();
            return rc;
        }
        expiration = expiration * 1000;

        try {

            log("submitOrder(" + label + ", " + symbol + ", " + oCommand + ", " + volume + "," + price + ", " + slippage + ", " + stoploss + ", " + takeprofit + ", " + 0 + ", " + comment + ")");
            IOrder order = engine.submitOrder(label, symbol, oCommand, volume, price, slippage, stoploss, takeprofit, expiration, comment);

            // while (order.getId() == null) {
            order.waitForUpdate(2000);
            // }
            if (order.getOrderCommand() == IEngine.OrderCommand.BUY || order.getOrderCommand() == IEngine.OrderCommand.SELL) {
                if (order.getState() == IOrder.State.OPENED) {
                    order.waitForUpdate(2000);
                }
            }
            try {
                String group = order.getId();
                if (group != null) {
                    rc = Integer.parseInt(group);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        } catch (JFException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        return rc;

    }

    private IEngine.OrderCommand getOrderCommand(int cmd) {
        IEngine.OrderCommand oCommand = null;
        switch (cmd) {
        case OP_BUY:// Buying position.
            oCommand = IEngine.OrderCommand.BUY;
            break;
        case OP_SELL:// Selling position.
            oCommand = IEngine.OrderCommand.SELL;
            break;
        case OP_BUYLIMIT:// Buy limit pending position.
            oCommand = IEngine.OrderCommand.BUYLIMIT;
            break;
        case OP_SELLLIMIT:// Sell limit pending position.
            oCommand = IEngine.OrderCommand.SELLLIMIT;
            break;
        case OP_BUYSTOP:// Buy stop pending position.
            oCommand = IEngine.OrderCommand.BUYSTOP;
            break;
        case OP_SELLSTOP:// Sell stop pending position.
            oCommand = IEngine.OrderCommand.SELLSTOP;
            break;
        case OP_BUYLIMIT_BIDOFFER:// Buy limit pending position.
            oCommand = IEngine.OrderCommand.PLACE_BID;
            break;
        case OP_SELLLIMIT_BIDOFFER:// Sell limit pending position.
            oCommand = IEngine.OrderCommand.PLACE_OFFER;
            break;
        default:
            notSupported();
            break;
        }
        return oCommand;
    }

    /*
     * Modification of characteristics for the previously opened position or
     * pending orders. If the function succeeds, the returned value will be
     * TRUE. If the function fails, the returned value will be FALSE. To get the
     * detailed error information, call GetLastError() function. Notes: Open
     * price and expiration time can be changed only for pending orders. If
     * unchanged values are passed as the function parameters, the error 1
     * (ERR_NO_RESULT) will be generated. Pending order expiration time can be
     * disabled in some trade servers. In this case, when a non-zero value is
     * specified in the expiration parameter, the error 147
     * (ERR_TRADE_EXPIRATION_DENIED) will be generated.
     */
    protected boolean OrderModify(int ticket, double price, double stoploss, double takeprofit, long expiration) {
        return OrderModify(ticket, price, stoploss, takeprofit, expiration, null);
    }

    protected boolean OrderModify(int ticket, double price, double stoploss, double takeprofit, long expiration, Color arrow_color) {
        price = box.roundHalfPip(price);
        stoploss = box.roundHalfPip(stoploss);
        takeprofit = box.roundHalfPip(takeprofit);
        expiration = expiration * 1000;

        boolean rc = false;
        IOrder toModify = engine.getOrderById(ticket + "");
        if (toModify != null) {
            double oldPrice = toModify.getOpenPrice();
            double oldStopLoss = toModify.getStopLossPrice();
            double oldTakeProfit = toModify.getTakeProfitPrice();
            double oldExpiration = toModify.getGoodTillTime() / 1000;

            if (oldPrice == price && oldStopLoss == stoploss && oldTakeProfit == takeprofit) {
                lastError = ERR_NO_RESULT;
                return rc;
            }
            if (toModify.getState() != IOrder.State.FILLED && oldPrice != price) {
                log("OrderModify - price change to " + price);
                try {
                    toModify.setOpenPrice(price);
                    toModify.waitForUpdate(5000);
                    rc = true;
                } catch (JFException e) {
                    e.printStackTrace();
                }

            }
            if (oldStopLoss != stoploss) {
                log("OrderModify - stoploss change to " + stoploss);
                try {
                    toModify.setStopLossPrice(stoploss);
                    toModify.waitForUpdate(5000);
                    rc = true;
                } catch (JFException e) {
                    e.printStackTrace();
                }
            }
            if (oldTakeProfit != takeprofit) {
                log("OrderModify - takeprofit change to " + takeprofit);
                try {
                    toModify.setTakeProfitPrice(takeprofit);
                    toModify.waitForUpdate(5000);
                    rc = true;
                } catch (JFException e) {
                    e.printStackTrace();
                }
            }
            if (expiration != oldExpiration && (toModify.getOrderCommand() == IEngine.OrderCommand.PLACE_BID || toModify.getOrderCommand() == IEngine.OrderCommand.PLACE_OFFER)) {
                log("OrderModify - expiration change to " + expiration);
                try {
                    toModify.setGoodTillTime(expiration);
                    toModify.waitForUpdate(5000);
                    rc = true;
                } catch (JFException e) {
                    e.printStackTrace();
                }
            }

            rc = true;
        }
        return rc;

    }

    /*
     * Returns swap value for the currently selected order. Note: The order must
     * be previously selected by the OrderSelect() function.
     */
    protected double OrderSwap() {
        // not implemented
        return 0;
    }

    /*
     * Returns expiration date for the selected pending order. Note: The order
     * must be previously selected by the OrderSelect() function.
     */
    protected long OrderExpiration() {
        return 0;
    }

    // -----------------------------------------------------------------------------------------------
    // Checkup
    // -----------------------------------------------------------------------------------------------
    //
    private int lastError = 0;

    // The function returns the last occurred error, then the value of special
    // last_error variable where the last error code is stored will be
    // zeroized. So, the next call for GetLastError() will return 0.
    protected int GetLastError() {
        return lastError;
    }

    /*
     * Returns TRUE if the expert is allowed to trade and a thread for trading
     * is not occupied, otherwise returns FALSE. See also IsDllsAllowed(),
     * IsLibrariesAllowed(), IsTradeContextBusy().
     */
    protected boolean IsTradeAllowed() {
        return true;
    }

    /*
     * Returns TRUE if a thread for trading is occupied by another expert
     * advisor, otherwise returns FALSE.
     */
    protected boolean IsTradeContextBusy() {
        return false;
    }

    /*
     * Returns TRUE if the program (an expert or a script) has been commanded to
     * stop its operation, otherwise returns FALSE. The program can continue
     * operation for 2.5 seconds more before the client terminal stops its
     * performing forcedly.
     */
    protected boolean IsStopped() {
        return false;

    }

    /**
     * The function returns the status of the main connection between client
     * terminal and server that performs data pumping. It returns TRUE if
     * connection to the server was successfully established, otherwise, it
     * returns FALSE
     * 
     * @return
     */
    protected boolean IsConnected() {
        return true;
    }

    /**
     * 
     * Returns TRUE if expert runs in the strategy tester optimisation mode,
     * otherwise returns FALSE.
     */
    protected boolean IsOptimization() {
        return false;
    }

    /**
     * 
     * @return Returns TRUE if expert runs in the testing mode, otherwise
     *         returns FALSE.
     */
    protected boolean IsTesting() {
        return engine.getType() == IEngine.Type.TEST;
    }

    /**
     * 
     * @return Returns TRUE if the expert runs on a demo account, otherwise
     *         returns FALSE.
     */
    protected boolean IsDemo() {
        return engine.getType() == IEngine.Type.DEMO;
    }

    /*
     * 
     * Returns TRUE if expert adwisors are enabled for running, otherwise
     * returns FALSE.
     */
    protected boolean IsExpertEnabled() {
        return true;
    }

    protected String ErrorDescription(int errCode) {
        return "Error codes not defined in converter";
    }

    // -----------------------------------------------------------------------------------------------

    // Technical indicators

    // -----------------------------------------------------------------------------------------------

    /**
     * Calculates the Accumulation/Distribution indicator and returns its value.
     * 
     * @param symbol
     *            - Symbol name of the security on the data of which the
     *            indicator will be calculated. NULL means the current symbol.
     * @param timeframe
     *            - Timeframe. It can be any of Timeframe enumeration values. 0
     *            means the current chart timeframe.
     * @param shift
     *            - Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     * @throws JFException
     */

    protected double iAD(Instrument symbol, int timeframe, int shift) {
        if (symbol == null) {
            symbol = currentInstrument;
        }
        double result = 0;
        Period convertedPeriod = convertPeriod(timeframe);
        try {
            result = indicators.ad(symbol, convertedPeriod, OfferSide.BID, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return result;
    };

    /**
     * Calculates the Bill Williams' Alligator and returns its value.
     * 
     * @param symbol
     *            - Symbol name of the security on the data of which the
     *            indicator will be calculated. NULL means the current symbol.
     * @param timeframe
     *            - Timeframe. It can be any of Timeframe enumeration values. 0
     *            means the current chart timeframe.
     * @param jaw_period
     *            - Blue line averaging period (Alligator's Jaw).
     * @param jaw_shift
     *            - Blue line shift relative to the chart.
     * @param teeth_period
     *            - Red line averaging period (Alligator's Teeth).
     * @param teeth_shift
     *            - Red line shift relative to the chart.
     * @param lips_period
     *            - Green line averaging period (Alligator's Lips).
     * @param lips_shift
     *            - Green line shift relative to the chart.
     * @param ma_method
     *            - MA method. It can be any of Moving Average methods.
     * @param applied_price
     *            - Applied price. It can be any of Applied price enumeration
     *            values.
     * @param mode
     *            - Data source, identifier of a line of the indicator. It can
     *            be any of the following values: MODE_GATORJAW - Gator Jaw
     *            (blue) balance line, MODE_GATORTEETH - Gator Teeth (red)
     *            balance line, MODE_GATORLIPS - Gator Lips (green) balance
     *            line.
     * @param shift
     *            - Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     * @throws JFException
     */

    protected double iAlligator(Instrument symbol, int timeframe, int jaw_period, int jaw_shift, int teeth_period, int teeth_shift, int lips_period, int lips_shift, int ma_method, int applied_price, int mode, int shift) {
        if (symbol == null) {
            symbol = currentInstrument;
        }

        double result = 0;
        Period period = convertPeriod(timeframe);
        AppliedPrice appliedPrice = convertAppliedPrice(applied_price);

        OfferSide side = OfferSide.BID;
        try {
            result = indicators.alligator(symbol, period, side, appliedPrice, jaw_period, teeth_period, lips_period, shift)[0];
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return result;
    };

    /**
     * Calculates the Commodity channel index and returns its value.
     * 
     * @param symbol
     *            - Symbol name of the security on the data of which the
     *            indicator will be calculated. NULL means the current symbol.
     * @param timeframe
     *            - Timeframe. It can be any of Timeframe enumeration values. 0
     *            means the current chart timeframe.
     * @param period
     *            - Averaging period for calculation.
     * @param applied_price
     *            - Applied price. It can be any of Applied price enumeration
     *            values.
     * @param shift
     *            - Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     * @throws JFException
     */

    protected double iCCI(Instrument symbol, int timeframe, int period, int applied_price, int shift) {
        if (symbol == null) {
            symbol = currentInstrument;
        }

        double result = 0;
        Period convertedPeriod = convertPeriod(timeframe);
        try {
            result = indicators.cci(symbol, convertedPeriod, OfferSide.BID, period, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return result;
    }

    /**
     * Calculation of the Commodity Channel Index on data stored in a numeric
     * array. Unlike iCCI(...), the iCCIOnArray function does not take data by
     * symbol name, timeframe, the applied price. The price data must be
     * previously prepared. The indicator is calculated from left to right. To
     * access to the array elements as to a series array (i.e., from right to
     * left), one has to use the ArraySetAsSeries function.
     * 
     * @param array
     *            - Array with data.
     * @param total
     *            - The number of items to be counted.
     * @param period
     *            - Averaging period for calculation.
     * @param shift
     *            - Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     */
    protected double iCCIOnArray(double array[], int total, int period, int shift) {
        double rc = 0;
        try {
            IIndicator maInd = indicators.getIndicator("CCI");
            maInd.setOptInputParameter(0, period);
            int lookback = maInd.getLookback();
            maInd.setInputParameter(0, array);
            double[] outArray = new double[total];
            maInd.setOutputParameter(0, outArray);
            maInd.calculate(lookback, total);
            rc = outArray[shift];
        } catch (Exception e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;

    }

    /**
     * Calculates the Force index and returns its value.
     * 
     * @param symbol
     *            - Symbol name of the security on the data of which the
     *            indicator will be calculated. NULL means the current symbol.
     * @param timeframe
     *            - Timeframe. It can be any of Timeframe enumeration values. 0
     *            means the current chart timeframe.
     * @param period
     *            - Averaging period for calculation.
     * @param ma_method
     *            - MA method. It can be any of Moving Average method
     *            enumeration value.
     * @param applied_price
     *            - Applied price. It can be any of Applied price enumeration
     *            values.
     * @param shift
     *            - Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     * @throws JFException
     */

    protected double iForce(Instrument symbol, int timeframe, int period, int ma_method, int applied_price, int shift) {
        if (symbol == null) {
            symbol = currentInstrument;
        }
        double result = 0;
        Period convertedPeriod = convertPeriod(timeframe);
        AppliedPrice appliedPrice = convertAppliedPrice(applied_price);
        try {
            result = indicators.floor(symbol, convertedPeriod, OfferSide.BID, appliedPrice, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return result;
    };

    /**
     * Calculates the Fractals and returns its value.
     * 
     * @param symbol
     *            - Symbol name of the security on the data of which the
     *            indicator will be calculated. NULL means the current symbol.
     * @param timeframe
     *            - Timeframe. It can be any of Timeframe enumeration values. 0
     *            means the current chart timeframe.
     * @param mode
     *            - Indicator line index. It can be any of the Indicators line
     *            identifiers enumeration value.
     * @param shift
     *            - Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     * @throws JFException
     */

    protected double iFractals(Instrument symbol, int timeframe, int mode, int shift) {
        if (symbol == null) {
            symbol = currentInstrument;
        }

        double result = 0;
        Period convertedPeriod = convertPeriod(timeframe);
        try {
            result = indicators.fractal(symbol, convertedPeriod, OfferSide.BID, mode, shift)[0];
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return result;
    }

    /**
     * Calculates the Money flow index and returns its value.
     * 
     * @param symbol
     *            - Symbol name of the security on the data of which the
     *            indicator will be calculated. NULL means the current symbol.
     * @param timeframe
     *            - Timeframe. It can be any of Timeframe enumeration values. 0
     *            means the current chart timeframe.
     * @param period
     *            - Period (amount of bars) for calculation of the indicator.
     * @param shift
     *            - Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     * @throws JFException
     */

    protected double iMFI(Instrument symbol, int timeframe, int period, int shift) {
        if (symbol == null) {
            symbol = currentInstrument;
        }

        double result = 0;
        Period convertedPeriod = convertPeriod(timeframe);
        try {
            result = indicators.mfi(symbol, convertedPeriod, OfferSide.BID, period, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return result;
    }

    /**
     * Calculates the On Balance Volume indicator and returns its value.
     * 
     * @param symbol
     *            - Symbol name of the security on the data of which the
     *            indicator will be calculated. NULL means the current symbol.
     * @param timeframe
     *            - Timeframe. It can be any of Timeframe enumeration values. 0
     *            means the current chart timeframe.
     * @param applied_price
     *            - Applied price. It can be any of Applied price enumeration
     *            values.
     * @param shift
     *            - Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     * @throws JFException
     */

    protected double iOBV(Instrument symbol, int timeframe, int applied_price, int shift) {
        if (symbol == null) {
            symbol = currentInstrument;
        }

        double result = 0;
        Period convertedPeriod = convertPeriod(timeframe);
        AppliedPrice appliedPrice = convertAppliedPrice(applied_price);

        try {
            result = indicators.obv(symbol, convertedPeriod, OfferSide.BID, appliedPrice, OfferSide.ASK, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return result;
    };

    /**
     * Calculates the Relative Vigor index and returns its value.
     * 
     * @param symbol
     *            - Symbol name of the security on the data of which the
     *            indicator will be calculated. NULL means the current symbol.
     * @param timeframe
     *            - Timeframe. It can be any of Timeframe enumeration values. 0
     *            means the current chart timeframe.
     * @param period
     *            - Number of periods for calculation.
     * @param mode
     *            - Indicator line index. It can be any of Indicators line
     *            identifiers enumeration value.
     * @param shift
     *            - Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     * @throws JFException
     */
    protected double iRVI(Instrument symbol, int timeframe, int period, int mode, int shift) {
        if (symbol == null) {
            symbol = currentInstrument;
        }

        double result = 0;
        Period convertedPeriod = convertPeriod(timeframe);
        // AppliedPrice appliedPrice =
        // CommonHelpers.convertAppliedPrice(applied_price);

        try {
            result = indicators.rvi(symbol, convertedPeriod, OfferSide.BID, period, shift)[0];
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return result;
    };

    // Calculates the Relative strength index and returns its value.
    protected double iRSI(Instrument symbol, int timeframe, int timePeriod, int applied_price, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);
        AppliedPrice convertedAppliedPrice = convertAppliedPrice(applied_price);

        try {
            rc = indicators.rsi(symbol, convertedPeriod, OfferSide.BID, convertedAppliedPrice, timePeriod, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;

    }

    // Calculates the Moving average indicator and returns its value.
    protected double iMA(Instrument symbol, int timeframe, int timePeriod, int ma_shift, int ma_method, int applied_price, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        if (ma_shift != 0) {
            notSupported();
            return rc;
        }

        Period convertedPeriod = convertPeriod(timeframe);
        AppliedPrice convertedAppliedPrice = convertAppliedPrice(applied_price);
        MaType maType = convertMaType(ma_method);
        try {
            rc = indicators.ma(symbol, convertedPeriod, OfferSide.BID, convertedAppliedPrice, timePeriod, maType, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;
    }

    /**
     * Calculation of the Moving Average on data stored in a numeric array.
     * Unlike iMA(...), the iMAOnArray function does not take data by symbol
     * name, timeframe, the applied price. The price data must be previously
     * prepared. The indicator is calculated from left to right.
     * 
     * @param array
     *            - Array with data.
     * @param total
     *            - The number of items to be counted. 0 means whole array.
     * @param period
     *            Averaging period for calculation.
     * @param ma_shift
     *            MA shift
     * @param ma_method
     *            MA method. It can be any of the Moving Average method
     *            enumeration value.
     * @param shift
     *            Index of the value taken from the indicator buffer (shift
     *            relative to the current bar the given amount of periods ago).
     * @return
     * 
     *         Thanks to Dmitry Shohov for contributing this method (2009-08-19)
     * 
     */
    protected double iMAOnArray(double[] array, int total, int period, int ma_shift, int ma_method, int shift) {
        double rc = 0;
        if (ma_shift != 0) {
            notSupported();
            return rc;
        }
        MaType maType = convertMaType(ma_method);

        try {
            IIndicator maInd = indicators.getIndicator(maType.toString());
            maInd.setOptInputParameter(0, period);
            int lookback = maInd.getLookback();
            maInd.setInputParameter(0, array);
            double[] outArray = new double[array.length - lookback];
            maInd.setOutputParameter(0, outArray);
            maInd.calculate(lookback, array.length - 1);
            rc = outArray[shift];
        } catch (Exception e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;

    }

    // Calculates the Momentum indicator and returns its value.
    protected double iMomentum(Instrument symbol, int timeframe, int period, int applied_price, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }

        Period convertedPeriod = convertPeriod(timeframe);
        AppliedPrice convertedAppliedPrice = convertAppliedPrice(applied_price);
        try {
            // public double mom(Instrument instrument, Period period, OfferSide
            // side, AppliedPrice appliedPrice, int timePeriod, int shift)
            // throws JFException;
            rc = indicators.mom(symbol, convertedPeriod, OfferSide.BID, convertedAppliedPrice, period, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;

    }

    // Calculates the Parabolic Stop and Reverse system and returns its value.
    protected double iSAR(Instrument symbol, int timeframe, double step, double maximum, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }

        Period convertedPeriod = convertPeriod(timeframe);

        try {
            rc = indicators.sar(symbol, convertedPeriod, OfferSide.BID, step, maximum, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;

    }

    // Calculates the Moving averages convergence/divergence and returns its
    // value. In the systems where OsMA is called MACD Histogram, this indicator
    // is displayed as two lines.
    // In the Client Terminal, the Moving Average Convergence/Divergence is
    // drawn as a histogram.
    protected double iMACD(Instrument symbol, int timeframe, int fast_ema_period, int slow_ema_period, int signal_period, int applied_price, int mode, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }

        // public double[] macd(Instrument instrument, Period period, OfferSide
        // side, AppliedPrice appliedPrice, int fastPeriod, int slowPeriod, int
        // signalPeriod, int shift) throws JFException;

        Period convertedPeriod = convertPeriod(timeframe);
        AppliedPrice convertedAppliedPrice = convertAppliedPrice(applied_price);
        // MaType maType = convertMaType(ma_method);
        double[] res = null;
        try {
            res = indicators.macd(symbol, convertedPeriod, OfferSide.BID, convertedAppliedPrice, fast_ema_period, slow_ema_period, signal_period, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }

        if (res != null) {
            if (mode == MODE_MAIN) {
                rc = res[0];
            } else if (mode == MODE_SIGNAL) {
                rc = res[1];
            } else {
                notSupported();
            }
        }
        return rc;

    }

    /**
     * Calculates the Standard Deviation indicator and returns its value.
     * Parameters: symbol - Symbol the data of which should be used to calculate
     * indicator. NULL means the current symbol. timeframe - Timeframe. It can
     * be any of Timeframe enumeration values. 0 means the current chart
     * timeframe. ma_period - MA period. ma_shift - MA shift. ma_method - MA
     * method. It can be any of Moving Average method enumeration value.
     * applied_price - Applied price. It can be any of Applied price enumeration
     * values. shift - Index of the value taken from the indicator buffer (shift
     * relative to the current bar the given amount of periods ago). Sample:
     * 
     * double val=iStdDev(NULL,0,10,0,MODE_EMA,PRICE_CLOSE,0);
     */
    protected double iStdDev(Instrument symbol, int timeframe, int ma_period, int ma_shift, int ma_method, int applied_price, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }

        Period convertedPeriod = convertPeriod(timeframe);
        AppliedPrice convertedAppliedPrice = convertAppliedPrice(applied_price);
        try {
            // Instrument instrument, Period period, OfferSide side,
            // AppliedPrice appliedPrice, int timePeriod, double nbDev, int
            // shift
            rc = indicators.stdDev(symbol, convertedPeriod, OfferSide.BID, convertedAppliedPrice, ma_period, 1, shift);// adx(symbol,
            // convertedPeriod,
            // OfferSide.BID,
            // timePeriod,
            // shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;
    }

    /**
     * Calculates the Indicator of the average true range and returns its value.
     * Parameters: symbol - Symbol the data of which should be used to calculate
     * indicator. NULL means the current symbol. timeframe - Timeframe. It can
     * be any of Timeframe enumeration values. 0 means the current chart
     * timeframe. period - Averaging period for calculation. shift - Index of
     * the value taken from the indicator buffer (shift relative to the current
     * bar the given amount of periods ago). Sample:
     * 
     * if(iATR(NULL,0,12,0)>iATR(NULL,0,20,0)) return(0);
     */

    protected double iATR(Instrument symbol, int timeframe, int period, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);

        try {
            rc = indicators.atr(symbol, convertedPeriod, OfferSide.BID, period, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;
    }

    // Calculates the Movement directional index and returns its value.
    // Commite
    protected double iADX(Instrument symbol, int timeframe, int timePeriod, int applied_price, int mode, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);
        try {
            if (mode == MODE_MAIN) {
                rc = indicators.adx(symbol, convertedPeriod, OfferSide.BID, timePeriod, shift);
            } else if (mode == MODE_MINUSDI) {
                rc = indicators.minusDi(symbol, currentPeriod, OfferSide.BID, timePeriod, shift);
            } else if (mode == MODE_PLUSDI) {
                rc = indicators.plusDi(symbol, currentPeriod, OfferSide.BID, timePeriod, shift);
            } else {
                notSupported();
            }
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;
    }

    // Calculates the Movement directional index and returns its value.

    /*
     * Calculates the DeMarker indicator and returns its value.
     */
    protected double iDeMarker(Instrument symbol, int timeframe, int timePeriod, int shift) {

        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }

        Period convertedPeriod = convertPeriod(timeframe);
        try {
            rc = indicators.td_i(symbol, convertedPeriod, OfferSide.BID, timePeriod, shift)[0];
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;
    }

    /*
     * Calculates the Bollinger bands indicator and returns its value.
     */
    protected double iBands(Instrument symbol, int timeframe, int timePeriod, int deviation, int bands_shift, int applied_price, int mode, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        if (bands_shift != 0) {
            notSupported();
            return rc;
        }
        Period convertedPeriod = convertPeriod(timeframe);
        AppliedPrice convertedAppliedPrice = convertAppliedPrice(applied_price);

        double[] res = null;
        try {
            res = indicators.bbands(symbol, convertedPeriod, OfferSide.BID, convertedAppliedPrice, timePeriod, deviation, deviation, MaType.SMA, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        if (res != null) {
            if (mode == MODE_UPPER) {
                rc = res[0];
            } else if (mode == MODE_LOWER) {
                rc = res[2];
            } else {
                notSupported();
            }
        }

        return rc;

    }

    /* Calculates the Stochastic oscillator and returns its value. */
    protected double iStochastic(Instrument symbol, int timeframe, int kPeriod, int dPeriod, int slowing, int method, int price_field, int mode, int shift) {
        double rc = 0;

        if (symbol == null) {
            symbol = currentInstrument;
        }
        Period convertedPeriod = convertPeriod(timeframe);

        MaType maType = convertMaType(method);

        double[] res = null;
        try {
            res = indicators.stoch(symbol, convertedPeriod, OfferSide.BID, kPeriod, slowing, maType, dPeriod, maType, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        if (res != null) {
            if (mode == MODE_MAIN) {
                rc = res[0];
            } else if (mode == MODE_SIGNAL) {
                rc = res[1];
            } else {
                notSupported();
            }
        }

        return rc;

    }

    // Calculates the William Percentage Ratio
    // double iWPR( string symbol, int timeframe, int period, int shift)
    protected double iWPR(Instrument symbol, int timeframe, int period, int shift) {
        double rc = 0;
        if (symbol == null) {
            symbol = currentInstrument;
        }
        // double willr(Instrument instrument, Period period, OfferSide side,
        // int timePeriod, int shift)
        Period convertedPeriod = convertPeriod(timeframe);
        try {
            rc = indicators.willr(symbol, convertedPeriod, OfferSide.BID, period, shift);
        } catch (JFException e) {
            e.printStackTrace();
            context.stop();
        }
        return rc;
    }

    // -----------------------------------------------------------------------------------------------
    // Window functions
    // -----------------------------------------------------------------------------------------------

    // Returns the amount of minutes determining the used period (chart
    // timeframe).
    protected int Period() {
        return currentPeriodMQL;
    }

    // Refreshing of data in pre-defined variables and series arrays.
    // This function is used when expert advisor has been calculating for a long
    // time and needs data refreshing.
    // Returns TRUE if data are refreshed, otherwise returns FALSE.
    // The only reason for data cannot be refreshed is that they are the current
    // data of the client terminal.
    protected boolean RefreshRates() {
        // only reasonable replacement is just wait for fresh tick. so nothing
        // to do due to architecture.
        return false;
    }

    // Returns a text string with the name of the current financial instrument.
    protected Instrument Symbol() {
        return currentInstrument;
    }

    /*
     * 
     * 
     * 
     * 
     * 
     * WINDOWS
     */

    /**
     * 
     * Returns name of the executed expert, script, custom indicator, or
     * library, depending on the MQL4 program, from which this function has been
     * called.
     */
    protected String WindowExpertName() {
        return getClass().getSimpleName();
    }

    /**
     * @return the amount of bars visible on the chart.
     */
    protected int WindowBarsPerChart() {
        int result = 0;
        if (chart != null) {
            result = chart.getBarsCount();
        }
        return result;
    }

    /**
     * @return count of indicator windows on the chart (including main chart).
     */
    protected int WindowsTotal() {
        if (chart != null) {
            return chart.windowsTotal();
        }
        return 0;
    }

    /**
     * The function sets a flag hiding indicators called by the Expert Advisor.
     * After the expert has been tested and the appropriate chart opened, the
     * flagged indicators will not be drawn in the testing chart. Every
     * indicator called will first be flagged with the current hiding flag. It
     * must be noted that only those indicators can be drawn in the testing
     * chart that are directly called from the expert under test. NOT USED IN JF
     */

    protected void HideTestIndicators(boolean hide) {

    }

    /*
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     */
    // -----------------------------------------------------------------------------------------------
    // Conversion functions
    // -----------------------------------------------------------------------------------------------

    // A group of functions that provide conversion of data from one format into
    // another.

    /**
     * Rounds the floating point value to the given precision. Returns
     * normalized value of the double type. The calculated StopLoss and
     * TakeProfit values, as well as open price of pending orders must be
     * normalized with a precision the value of which is stored in the
     * pre-defined variable of Digits. Parameters: value - Floating point value.
     * digits - Precision format, number of digits after decimal point (0-8).
     * Sample:
     * 
     * double var1=0.123456789; Print(DoubleToStr(NormalizeDouble(var1,5),8));
     * // output: 0.12346000
     * 
     * DoubleToStr StrToDouble
     */
    protected double NormalizeDouble(double value, double precision) {
        return NormalizeDouble(value, (long) precision);
    }

    protected double NormalizeDouble(double value, long precision) {
        boolean negative = false;
        if (value < 0) {
            negative = true;
            value = -value;
        }
        if (value == 0) {
            return value;
        }
        double multiplier = 1;
        while (precision > 0) {
            multiplier *= 10;
            --precision;
        }
        while (precision < 0 && value * multiplier / 10 >= 1) {
            multiplier /= 10;
            ++precision;
        }
        while (value * multiplier < 1) {
            multiplier *= 10;
        }
        value *= multiplier;
        long longValue = (long) (value + 0.5d);
        value = ((double) longValue) / multiplier;
        return negative ? -value : value;
    }

    // Converts string representation of number to double type (double-precision
    // format with floating point).
    protected double StrToDouble(String str) {
        try {
            if (str == null) {
                return 0;
            }
            char c = dfPrint.getDecimalFormatSymbols().getGroupingSeparator();
            str = str.replace(c, ',');
            str = str.replaceAll(" ", "");
            str = str.replaceAll(",", "");
            return Double.parseDouble(str);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    // Returns text string with the specified numerical value converted into a
    // specified precision format.
    protected String DoubleToStr(double value, int digits) {
        return dfPrint.format(NormalizeDouble(value, digits));
    }

    /*
     * Converts string in the format "yyyy.mm.dd hh:mi" to datetime type (the
     * amount of seconds that have passed since 1 Jan., 1970).
     */
    private SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy.MM.dd HH:mm");

    private SimpleDateFormat dateFormat2 = new SimpleDateFormat("HH:mm");

    private SimpleDateFormat dateFormat3 = new SimpleDateFormat("yyyy.MM.dd");

    protected long StrToTime(String value) {
        dateFormat1.setTimeZone(TimeZone.getTimeZone("GMT"));
        dateFormat2.setTimeZone(TimeZone.getTimeZone("GMT"));
        dateFormat3.setTimeZone(TimeZone.getTimeZone("GMT"));

        long rc = 0;
        Date date = null;
        try {
            date = dateFormat1.parse(value);
        } catch (ParseException e) {

        }
        if (date != null) {
            rc = date.getTime() / 1000;
        }
        return rc;

    }

    /**
     * Converts value containing time in seconds that has passed since January
     * 1, 1970, into a string of "yyyy.mm.dd hh:mi" format.
     * 
     * @param value
     *            - Positive amount of seconds that have passed since 00:00,
     *            January 1, 1970.
     * @param mode
     *            - Optional data output mode can be one or combination of:
     *            TIME_DATE gets result as "yyyy.mm.dd", TIME_MINUTES gets
     *            result as "hh:mi", TIME_SECONDS gets result as "hh:mi:ss".
     * @return
     */

    protected static final int TIME_DATE = 1;

    protected static final int TIME_MINUTES = 2;

    protected static final int TIME_SECONDS = 4;

    protected String TimeToStr(long value) {
        return TimeToStr(value, (TIME_DATE | TIME_MINUTES));
    }

    protected String TimeToStr(long value, int mode) {
        dateFormat1.setTimeZone(TimeZone.getTimeZone("GMT"));
        dateFormat2.setTimeZone(TimeZone.getTimeZone("GMT"));
        dateFormat3.setTimeZone(TimeZone.getTimeZone("GMT"));

        Date date = new Date(value * 1000);

        String rc = null;

        switch (mode) {
        case (TIME_DATE | TIME_MINUTES):
            rc = dateFormat1.format(date);
            break;

        default:
            notSupported();
            break;
        }

        return rc;

    }

    /**
     * Converts string containing the value character representation into a
     * value of the int (integer) type.
     * 
     * @param value
     *            String containing the integer character representation format.
     * @return
     */
    protected int StrToInteger(String value) {
        return Integer.parseInt(value);
    }

    // ================================================================================

    // ---------------------------------------
    private Period convertPeriod(int timeframe) {
        Period rc = null;
        switch (timeframe) {
        case 0:
            rc = currentPeriod;
            break;
        case PERIOD_M1:
            rc = Period.ONE_MIN;
            break;
        case PERIOD_M5:
            rc = Period.FIVE_MINS;
            break;
        case PERIOD_M15:
            rc = Period.FIFTEEN_MINS;
            break;
        case PERIOD_M30:
            rc = Period.THIRTY_MINS;
            break;
        case PERIOD_H1:
            rc = Period.ONE_HOUR;
            break;
        case PERIOD_H4:
            rc = Period.FOUR_HOURS;
            break;
        case PERIOD_D1:
            rc = Period.DAILY;
            break;

        case PERIOD_W1:
            rc = Period.WEEKLY;
            break;
        case PERIOD_MN1:
            rc = Period.MONTHLY;
            break;
        default:
            notSupported();
            break;
        }

        return rc;
    }

    private IChart.Type convertChartType(int type) {
        IChart.Type rc = null;

        switch (type) {
        case OBJ_VLINE:
            rc = IChart.Type.VLINE;
            break;
        case OBJ_HLINE:
            rc = IChart.Type.HLINE;
            break;
        case OBJ_TREND:
            rc = IChart.Type.TREND;
            break;
        case OBJ_TRENDBYANGLE:
            rc = IChart.Type.TRENDBYANGLE;
            break;
        case OBJ_REGRESSION:
            rc = IChart.Type.REGRESSION;
            break;
        case OBJ_CHANNEL:
            rc = IChart.Type.CHANNEL;
            break;
        case OBJ_STDDEVCHANNEL:
            rc = IChart.Type.STDDEVCHANNEL;
            break;
        case OBJ_GANNLINE:
            rc = IChart.Type.GANNLINE;
            break;
        case OBJ_GANNFAN:
            rc = IChart.Type.GANNFAN;
            break;
        case OBJ_GANNGRID:
            rc = IChart.Type.GANNGRID;
            break;
        case OBJ_FIBO:
            rc = IChart.Type.FIBO;
            break;
        case OBJ_FIBOTIMES:
            rc = IChart.Type.FIBOTIMES;
            break;
        case OBJ_FIBOFAN:
            rc = IChart.Type.FIBOFAN;
            break;
        case OBJ_FIBOARC:
            rc = IChart.Type.FIBOARC;
            break;
        case OBJ_EXPANSION:
            rc = IChart.Type.EXPANSION;
            break;
        case OBJ_FIBOCHANNEL:
            rc = IChart.Type.FIBOCHANNEL;
            break;
        case OBJ_RECTANGLE:
            rc = IChart.Type.RECTANGLE;
            break;
        case OBJ_TRIANGLE:
            rc = IChart.Type.TRIANGLE;
            break;
        case OBJ_ELLIPSE:
            rc = IChart.Type.ELLIPSE;
            break;
        case OBJ_PITCHFORK:
            rc = IChart.Type.PITCHFORK;
            break;
        case OBJ_CYCLES:
            rc = IChart.Type.CYCLES;
            break;
        case OBJ_TEXT:
            rc = IChart.Type.TEXT;
            break;
        case OBJ_ARROW:
            rc = IChart.Type.SIGNAL_DOWN;
            break;
        case OBJ_LABEL:
            rc = IChart.Type.LABEL;
            break;

        default:
            break;
        }

        return rc;
    }

    private AppliedPrice convertAppliedPrice(int applied_price) {
        AppliedPrice rc = null;
        switch (applied_price) {
        case PRICE_CLOSE:
            rc = AppliedPrice.CLOSE;
            break;
        case PRICE_OPEN:
            rc = AppliedPrice.OPEN;
            break;
        case PRICE_HIGH:
            rc = AppliedPrice.HIGH;
            break;
        case PRICE_LOW:
            rc = AppliedPrice.LOW;
            break;
        case PRICE_MEDIAN:
            rc = AppliedPrice.MEDIAN_PRICE;
            break;
        case PRICE_TYPICAL:
            rc = AppliedPrice.TYPICAL_PRICE;
            break;
        case PRICE_WEIGHTED:
            rc = AppliedPrice.WEIGHTED_CLOSE;
            break;

        default:
            notSupported();
            break;
        }

        return rc;
    }

    private MaType convertMaType(int ma_method) {
        MaType rc = null;
        switch (ma_method) {
        case MODE_SMA:
            rc = MaType.SMA;
            break;
        case MODE_EMA:
            rc = MaType.EMA;
            break;

        default:
            notSupported();
            break;
        }

        return rc;

    }

    private BitSet bitSetFromInt(int integer) {
        BitSet bitSet = new BitSet();
        String binarStr = Integer.toBinaryString(integer);
        for (int i = 0; i < binarStr.length(); i++) {
            boolean bin = binarStr.charAt(i) == '1';
            bitSet.set(binarStr.length() - i - 1, bin);
        }

        /*
         * for (int i = binarStr.length() - 1; i >= 0; i--) { boolean bin =
         * binarStr.charAt(i) == '1'; bitSet.set(i, bin); }
         */
        return bitSet;

    }

    private int intFromBitset(BitSet bitSet) {
        StringBuilder builder = new StringBuilder();
        for (int i = bitSet.length() - 1; i >= 0; i--) {
            builder.append(bitSet.get(i) ? '1' : '0');
        }
        String str = builder.toString();
        if (str.length() == 0) {
            str = "0";
        }
        return Integer.valueOf(str, 2);
    }

    protected int bool2int(boolean bool) {
        return bool ? 1 : 0;
    }

    private DecimalFormat storeFormat = new DecimalFormat("#,##0.#", new DecimalFormatSymbols(Locale.US));

    /*
     * public boolean evaluation(boolean onlydemo, String tillDate) { return
     * box.evaluation(onlydemo, tillDate);
     * 
     * }
     */

    private double parseDouble(String str) {
        try {

            if (str == null) {
                return 0;
            }
            char c = storeFormat.getDecimalFormatSymbols().getGroupingSeparator();
            str = str.replace(c, ',');
            str = str.replaceAll(" ", "");
            str = str.replaceAll(",", "");
            return Double.parseDouble(str);
        } catch (Exception e) {

        }
        return 0;

    }

    private String formatDouble(double dd) {
        return storeFormat.format(dd);
    }

    private void notSupported() {
        throw new RuntimeException("Operation unsupported");
    }

    private static Random random = new Random();

    /*
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * ARRAY HELPER
     */
    /**
     * <p>
     * Converts an array of primitive doubles to objects.
     * </p>
     * 
     * <p>
     * This method returns <code>null</code> for a <code>null</code> input
     * array.
     * </p>
     * 
     * @param array
     *            a <code>double</code> array
     * @return a <code>Double</code> array, <code>null</code> if null array
     *         input
     */
    public static Double[] toObject(double[] array) {
        if (array == null) {
            return null;
        } else if (array.length == 0) {
            return new Double[0];
        }
        final Double[] result = new Double[array.length];
        for (int i = 0; i < array.length; i++) {
            result[i] = new Double(array[i]);
        }
        return result;
    }

    // ----------------------------------------------------------------------
    /**
     * <p>
     * Converts an array of object Doubles to primitives.
     * </p>
     * 
     * <p>
     * This method returns <code>null</code> for a <code>null</code> input
     * array.
     * </p>
     * 
     * @param array
     *            a <code>Double</code> array, may be <code>null</code>
     * @return a <code>double</code> array, <code>null</code> if null array
     *         input
     * @throws NullPointerException
     *             if array content is <code>null</code>
     */
    public static double[] toPrimitive(Double[] array) {
        if (array == null) {
            return null;
        } else if (array.length == 0) {
            return new double[0];
        }
        final double[] result = new double[array.length];
        for (int i = 0; i < array.length; i++) {
            result[i] = array[i].doubleValue();
        }
        return result;
    }

    /**
     * <p>
     * Converts an array of object Doubles to primitives handling
     * <code>null</code>.
     * </p>
     * 
     * <p>
     * This method returns <code>null</code> for a <code>null</code> input
     * array.
     * </p>
     * 
     * @param array
     *            a <code>Double</code> array, may be <code>null</code>
     * @param valueForNull
     *            the value to insert if <code>null</code> found
     * @return a <code>double</code> array, <code>null</code> if null array
     *         input
     */
    public static double[] toPrimitive(Double[] array, double valueForNull) {
        if (array == null) {
            return null;
        } else if (array.length == 0) {
            return new double[0];
        }
        final double[] result = new double[array.length];
        for (int i = 0; i < array.length; i++) {
            Double b = array[i];
            result[i] = (b == null ? valueForNull : b.doubleValue());
        }
        return result;
    }

    public static int[] arrayDimensionInfo(Object array) {
        ArrayList<Integer> dimSize = new ArrayList<Integer>();
        int dim = 0;
        Class c = array.getClass();
        while (c.isArray()) {
            dimSize.add(Array.getLength(c));
            c = c.getComponentType();
            dim++;
        }
        int[] info = new int[dimSize.size() + 1];
        info[0] = dim;
        for (int i = 0; i < info.length; i++) {
            info[i + 1] = dimSize.get(i);
        }
        return info;
    }

    /**
     * Reallocates an array with a new size, and copies the contents of the old
     * array to the new array.
     * 
     * @param oldArray
     *            the old array, to be reallocated.
     * @param newSize
     *            the new array size.
     * @return A new array with the same contents.
     */
    public static Object resizeArray(Object oldArray, int newSize) {
        int oldSize = java.lang.reflect.Array.getLength(oldArray);
        Class elementType = oldArray.getClass().getComponentType();
        Object newArray = java.lang.reflect.Array.newInstance(elementType, newSize);
        int preserveLength = Math.min(oldSize, newSize);
        if (preserveLength > 0)
            System.arraycopy(oldArray, 0, newArray, 0, preserveLength);
        return newArray;
    }

}
